Какие преобразования типов происходят?

#include "stdio.h"

int main()
{
    int x = -13701;
    unsigned int y = 3;
    signed short z = x / y;

    printf("z = %d\n", z);

    return 0;
}

Я ожидаю, что ответ будет -4567. Я получаю "z = 17278". Почему повышение этих номеров приводит к 17278?

Я выполнил это в Code Pad.


person Robert    schedule 23.09.2010    source источник


Ответы (4)


Преобразования скрытого типа:

signed short z = (signed short) (((unsigned int) x) / y);

Когда вы смешиваете подписанные и неподписанные типы, выигрывают неподписанные. x преобразуется в unsigned int, делится на 3, а затем этот результат преобразуется с понижением частоты в (со знаком) short. С 32-битными целыми числами:

(unsigned) -13701         == (unsigned) 0xFFFFCA7B // Bit pattern
(unsigned) 0xFFFFCA7B     == (unsigned) 4294953595 // Re-interpret as unsigned
(unsigned) 4294953595 / 3 == (unsigned) 1431651198 // Divide by 3
(unsigned) 1431651198     == (unsigned) 0x5555437E // Bit pattern of that result
(short) 0x5555437E        == (short) 0x437E        // Strip high 16 bits
(short) 0x437E            == (short) 17278         // Re-interpret as short

Кстати, ключевое слово signed не нужно. signed short — это более длинный способ сказать short. Единственный тип, который нуждается в явном signed, это char. char может быть подписанным или неподписанным в зависимости от платформы; все остальные типы всегда подписаны по умолчанию.

person John Kugelman    schedule 23.09.2010
comment
Возможно, стоит отметить, что в общем случае преобразование из подписанного в беззнаковое не основано на повторной интерпретации. На самом деле конверсия и реинтерпретация — очень, очень, очень разные вещи, и в данном случае мы имеем на самом деле конверсию, а не реинтерпретацию. - person AnT; 23.09.2010
comment
@AnT, что ты имеешь в виду? Мне кажется, что подписанные ‹-› беззнаковые приведения являются именно переинтерпретацией, потому что биты в памяти не меняются, а только то, какое целое число с основанием 10 они представляют. С другой стороны, короткие приведения типа int ‹-› могут на самом деле менять биты, так что это не просто переинтерпретация. - person Aditya Kashi; 07.01.2021
comment
@Aditya Kashi: приведение типов между подписанным и неподписанным является чисто концептуальным (не изменяет память) на современных аппаратных платформах просто потому, что эти платформы используют 2-дополнение для представления значений со знаком. Первоначальные стандарты C и C++ не требовали представления с дополнением до 2, но также поддерживали представление с дополнением до 1 и представление значения со знаком. В этих альтернативных знаковых представлениях преобразование больше не является концептуальным. Он меняет представление значения, а это означает, что преобразование и переинтерпретация дают разные результаты. - person AnT; 09.01.2021
comment
В последних версиях языковых стандартов было решено отказаться от поддержки дополнения до 1 и величины со знаком, так что теперь вы можете с уверенностью предположить, что преобразования между знаковым и беззнаковым ничего не меняют. - person AnT; 09.01.2021

Краткий ответ: дивизия сначала повышает x до unsigned. Только тогда результат возвращается к signed short.

Длинный ответ: прочитайте эту ветку SO.

person Eli Bendersky    schedule 23.09.2010

Проблемы возникают из-за unsigned int y. Действительно, x/y становится беззнаковым. Он работает с:

#include "stdio.h"

int main()
{
    int x = -13701;
    signed int y = 3;
    signed short z = x / y;

    printf("z = %d\n", z);

    return 0;
}
person ThR37    schedule 23.09.2010

Каждый раз, когда вы смешиваете «большие» знаковые и беззнаковые значения в аддитивных и мультипликативных арифметических операциях, беззнаковый тип «выигрывает», и оценка выполняется в домене беззнакового типа («большой» означает int и больше). Если исходное значение со знаком было отрицательным, оно сначала будет преобразовано в положительное значение без знака в соответствии с правилами преобразования знакового значения в беззнаковое. В вашем случае -13701 превратится в UINT_MAX + 1 - 13701 и результат будет использован в качестве дивиденда.

Обратите внимание, что результатом преобразования знакового значения в беззнаковое на типичной 32-разрядной платформе int будет значение без знака 4294953595. После деления на 3 получится 1431651198. Это значение слишком велико, чтобы его можно было принудительно ввести в объект short на платформе с 16-битным типом short. Попытка сделать это приводит к поведению, определяемому реализацией. Итак, если свойства вашей платформы такие же, как в моих предположениях, то ваш код производит поведение, определяемое реализацией. Формально говоря, "бессмысленное" значение 17278, которое вы получаете, является не чем иным, как конкретным проявлением этого поведения, определяемого реализацией. Возможно, что если вы скомпилируете свой код с включенной проверкой переполнения (если ваш компилятор их поддерживает), он будет ловить присваивание.

person AnT    schedule 23.09.2010
comment
На самом деле это не неопределенное поведение: стандарт говорит, что либо результат определяется реализацией, либо возникает сигнал, определяемый реализацией (преобразование в более узкий знаковый тип отличается от переполнения во время вычисления). - person caf; 24.09.2010
comment
@caf: Вы совершенно правы. Спасибо за исправление. Хм... Я помню, что сам несколько раз делал это поправочное замечание, но в данном случае я как-то забыл об этом :) - person AnT; 24.09.2010