Операция побитового сдвига в C для переменной uint64_t - PullRequest
3 голосов
/ 15 февраля 2012

У меня есть следующий пример кода:

uint64_t x, y;
x = ~(0xF<<24);
y = ~(0xFF<<24);

Результат будет:

x=0xfffffffff0ffffff
y=0xfffff

Может кто-нибудь объяснить разницу? Почему x рассчитывается для 64 бит, а y только для 32?

Ответы [ 4 ]

6 голосов
/ 15 февраля 2012

Операция по умолчанию - 32 бита.

x=~(0xf<<24);

Этот код может быть разобран на следующие шаги:

int32_t a;
a=0x0000000f;
a<<=24;   // a=0x0f000000;
a=~a;     // a=0xf0ffffff;
x=(uint64_t)a;  // x = 0xfffffffff0ffffff;

И,

y = ~(0xFF<<24);

int32_t a;
a=0x000000ff;
a<<=24;   // a=0xff000000;
a=~a;     // a=0x00ffffff;
x=(uint64_t)a;  // x = 0x000000000ffffff;
2 голосов
/ 15 февраля 2012

Поскольку 0x0f << 24 является положительным числом при просмотре как int, его знак расширяется до положительного числа, то есть до 0x00000000_0f000000 (подчеркивание только для удобства чтения, C не поддерживает этот синтаксис). Затем это превращается в то, что вы видите.

0xff << 24, с другой стороны, отрицательный, поэтому он расширяется по-разному.

1 голос
/ 15 февраля 2012

Другие плакаты показали, почему это происходит.Но чтобы получить ожидаемые результаты:

uint64_t x, y; 
x = ~(0xFULL<<24); 
y = ~(0xFFULL<<24);

Или вы можете сделать это (я не знаю, если это немного медленнее, чем выше):

uint64_t x, y; 
x = ~(uint64_t(0xF)<<24); 
y = ~(uint64_t(0xFF)<<24); 

Тогда:

x = 0xfffffffff0ffffff
y = 0xffffffff00ffffff
0 голосов
/ 15 февраля 2012

В вашей программе не определено поведение, поэтому может произойти что угодно.

  • Целочисленные литералы 0xF или 0xFF имеют тип int, что эквивалентно signed int. На этой конкретной платформе int, по-видимому, 32 бита.
  • Целочисленный литерал 24 также является (подписанным) int.
  • Когда компилятор оценивает операцию <<, оба операнда имеют (подпись) <code>int, поэтому неявные преобразования типов не выполняются. Поэтому результатом операции << также является (подпись) <code>int.
  • Значение 0xF << 24 = 0x0F000000 помещается в (со знаком) <code>int как неотрицательное значение, поэтому все в порядке.
  • Значение 0xFF << 24 = 0xFF000000 <em>не подходит in (подписано) int! Здесь вызывается неопределенное поведение, и может произойти все, что угодно.

ISO 9899: 2011 6.5.7 / 4:

"Результат E1 << E2 - это сдвинутые влево позиции E1 в битах E2; освобождены биты заполнены нулями. "/ - / </p>

"Если E1 имеет тип со знаком и неотрицательное значение, а E1 × 2E2 представимо в типе результата, тогда это итоговое значение; в противном случае поведение не определено.

Так что выражение 0xFF << 24 нельзя использовать. Программа может свободно печатать любое значение мусора после этого. </p>

Но если мы проигнорируем это и сосредоточимся на 0x0F <24: </p>

  • 0x0F000000 по-прежнему (подписано) int. Для этого применяется оператор ~.
  • Результат - 0xF0FFFFFF, который все еще является целым числом со знаком. И почти в любой системе этот 32-битный гекс равен отрицательному числу в дополнении до двух.
  • Это подписанное int преобразуется в тип uint64_t во время присваивания. Это выполняется в два этапа, сначала путем преобразования его в 64-разрядный со знаком, а затем путем преобразования 64-разрядного со знаком в 64-разрядный без знака.

Подобные ошибки приводят к тому, что стандарт кодирования MISRA-C содержит ряд правил, запрещающих небрежное использование целочисленных литералов в выражениях, подобных этому. Код, совместимый с MISRA-C, должен использовать суффикс u после каждого целочисленного литерала (MISRA-C: 2004 10.6), а коду не разрешается выполнять побитовые операции со знаковыми целыми числами (MISRA-C: 2004 12.7).

...