Почему ((unsigned char) 0x80) <24 получает знак, расширенный до 0xFFFFFFFF80000000 (64-разрядный)?
Следующая программа
#include <inttypes.h> /* printf(" %" PRIu32 "n"), my_uint32_t) */
#include <stdio.h> /* printf(), perror() */
int main(int argc, char *argv[])
{
uint64_t u64 = ((unsigned char)0x80) << 24;
printf("%" PRIX64 "n", u64);
/* uint64_t */ u64 = ((unsigned int)0x80) << 24;
printf("%016" PRIX64 "n", u64);
}
Производит
FFFFFFFF80000000
0000000080000000
В чем разница между
((unsigned char)0x80) и ((unsigned int)0x80) в этом контексте?Я предполагаю, что (unsigned char)0x80 повышается до (unsigned char)0xFFFFFFFFFFFFFF80, а затем смещается бит, но почему это преобразование думает, что unsigned char подписано?
Интересно также отметить, что
0x80 << 16 дает ожидаемый результат, 0x0000000000800000. 5 ответов:
Левый операнд оператора
<<подвергается целочисленному продвижению.(C99, 6.5. 7p3) " целочисленные продвижения выполняются на каждом из операндов."
Это означает следующее выражение:
((unsigned char)0x80) << 24Эквивалентно:
((int) (unsigned char)0x80) << 24Эквивалентно:
0x80 << 24, который задает знаковый бит
intв 32-разрядной системеint. Затем, когда0x80 << 24преобразуется вuint64_tв объявленииu64, расширение знака происходит для получения значения0xFFFFFFFF80000000.Редактировать:
Обратите внимание, что какМэтт Макнабб правильно добавил в комментариях, технически
0x80 << 24вызывает неопределенное поведение в C, поскольку результат не представляется в типе левого операнда<<. Если вы используетеgcc, текущая версия компилятора гарантирует , что в данный момент она не делает эту операцию неопределенной.
Компилятор C выполняетцелочисленные продвижения Перед выполнением сдвига.
Правило 6.3.1.1 в стандарте сказано:
Если
intможет представлять все значения исходного типа, то значение преобразуется вint; в противном случае оно преобразуется вunsigned int. Они называются целочисленными акциями.Так как все значения
unsigned charмогут быть представленыint,0x80преобразуется в знакint. То же самое не верно в отношенииunsigned int: некоторые из его значения не могут быть представлены в видеint, поэтому они остаютсяunsigned intпосле применения целочисленных повышений.
Странная часть преобразования происходит при преобразовании результата
<<из int32 в uint64. Вы работаете с 32-битной системой, поэтому размер целочисленного типа равен 32 битам. Следующий код:u64 = ((int) 0x80) << 24; printf("%llx\n", u64);Отпечатки пальцев:
FFFFFFFF80000000Потому что (0x80 0x8000000, который является 32-битным представлением -2147483648. Это число преобразуется в 64 бита путем умножения знакового бита и дает
0xFFFFFFFF80000000.
То, что вы наблюдаете, - это неопределенное поведение. C99 §6.5.7/4 описывает сдвиг влево следующим образом:
Результатом
E1 << E2являетсяE1сдвинутые влевоE2разрядные позиции; освобожденные биты заполняются нулями. ЕслиE1имеет беззнаковый тип, то значение результата равноE1× 2E2, уменьшено по модулю на единицу больше максимального значения, представленного в типе результата. ЕслиE1имеет знаковый тип и неотрицательное значение, иE1× 2E2есть представимый в типе результата, то это результирующее значение; в противном случае поведение не определено.В вашем случае
E1имеет значение 128, и его тип -int, а неunsigned char. Как уже упоминалось в других ответах, значениеповышается доintдо оценки. Задействованные операнды имеют знакint, а значение 128 сдвинутых влево 24 места равно 2147483648, что на единицу больше максимального значения, представленногоintв вашей системе. Следовательно, поведение вашей программы не определено.Чтобы избежать этого, вы можете убедиться, что тип
E1являетсяunsigned intпутем приведения типа к этому, а не кunsigned char.
Одна из главных трудностей с эволюцией стандарта с заключается в том, что к тому времени, когда были предприняты усилия по стандартизации языка, существовали не только реализации, которые делали определенные вещи по-разному друг от друга, но и был значительный объем кода, написанного для этих реализаций , которые опирались на эти поведенческие различия. Потому что создатели стандарта C хотели избежать запрета реализациям вести себя так, как ведут себя пользователи этих реализаций. можно было бы положиться на то, что некоторые части стандарта C-это настоящий беспорядок. Некоторые из худших аспектов касаются аспектов целочисленного продвижения, таких как тот, который вы наблюдали.
Концептуально, казалось бы, было бы более разумно иметьunsigned charдолжно способствоватьunsigned int, чемsigned int, по крайней мере, при использовании в качестве чего-либо иного, чем правый операнд оператора-. Комбинации других операторов могут давать большие результаты, но ни один оператор, кроме-, не может дать отрицательный результат. Чтобы понять, почемуsigned intбыл выбран несмотря на то, что результат не может быть отрицательным, рассмотрим следующее:int i1; unsigned char b1,b2; unsigned int u1; long l1,l2,l3; l1 = i1+u1; l2 = i1+b1; l3 = i1+(b1+b2);В языке Си нет механизма, с помощью которого операция между двумя различными типами могла бы привести к типу, который не является одним из оригиналов, поэтому первый оператор должен выполнять сложение как со знаком, так и без знака; без знака обычно дает несколько менее удивительные результаты, особенно учитывая, что целочисленные литералы по умолчанию подписаны (было бы очень странно, если бы добавление
1вместо1uк беззнаковому значению может сделать его отрицательным). Однако было бы удивительно, если бы третье утверждение могло превратить отрицательное значениеi1в большое беззнаковое число. Если первое утверждение выше дает результат без знака, но третье утверждение дает результат со знаком, то это означает, что(b1+b2)должно быть подписано.ИМХО, "правильный" способ решить проблемы, связанные с сигнатурностью, - это определить отдельные числовые типы, которые задокументировали поведение "обертывания". (как и нынешние беззнаковые типы), и по сравнению с теми, которые должны вести себя как целые числа,и имеют два типа типов, демонстрирующих различные правила продвижения. Реализации должны были бы поддерживать существующее поведение для кода, использующего существующие типы, но новые типы могли бы реализовать правила, которые были разработаны для удобства использования вместо совместимости.
Comments