Побитовое смещение (<<) странное поведение

gcc побитовое смещение (<<) странное поведение. Вот мой код:

#include <stdio.h>
#include <string.h>

void foo(int n){
  printf("1<<32:%d\n", 1<<32);
  printf("1<<(32-n):%d\n", 1<<(32-n));
}

int main(){
    foo(0);
}

Если я передам 0 в качестве параметра, результат может быть другим. Компиляция исходного кода:

$gcc main.c -o demo -lm -pthread -lgmp -lreadline 2>&1
main.c: In function 'foo':
main.c:5:3: warning: left shift count >= width of type [enabled by default]

Выполнение программы:

$demo

1<<32:0
1<<(32-n):1

Этот результат - то, что я получил от компиляции онлайн сайта

Как я могу сделать вывод функции foo 0, если я передам ей 0? (в настоящее время вместо него выводится 1)

6 ответов

Решение

Сдвиг на значение, равное или превышающее ширину повышенного типа левого операнда, является неопределенным поведением, поэтому вы должны специально проверять и избегать этого. Кроме того, сдвиг влево подписанного типа, который приводит к переполнению, также является неопределенным поведением, поэтому вам также следует избегать сдвига 31:

printf("1<<(32-n):%d\n", (n > 1 && n < 33) ? 1 << (32-n) : 0);

Это конкретное выражение использует 0 для случаев, которые иначе не определены, но вы можете обрабатывать их по-разному, если вам нужно.

Поскольку вы меняете 32-битные значения, сдвиг на 32 бита приведет к нулевому значению. Тем не менее, операция сдвига битов ЦП может сдвигаться только на 0–31 бит, поскольку все остальное, как правило, бесполезно и только усложнит вычисления.

Причина в том, что первый пример, 1<<32кажется, работает, что компилятор оптимизирует это для 0 во время компиляции, одновременно печатая предупреждение. Другой пример, 1<<(32-n)однако имеет значение сдвига, которое не может быть определено во время компиляции (таким образом, также нет предупреждения). Вместо этого процессор использует результат вычитания 32 - n == 32 для своей операции сдвига, но процессор берет только пять младших битов и, таким образом, переполняется до 0, и в результате 1 << 0 == 1,

Чтобы обойти это, вам придется либо особый случай n == 0используйте более широкий тип данных или просто используйте меньше битов.

Согласно стандарту C ISO 9899:1999 глава 6.5.7 Bitwise shift operators:

Целочисленные продвижения выполняются на каждом из операндов. Тип результата - тип повышенного левого операнда. Если значение правого операнда отрицательно или больше или равно ширине повышенного левого операнда, поведение не определено.

Странно, что компилятор обрабатывает два выражения по-разному. Но так как это в любом случае вызывает неопределенное поведение, это не проблема. Что вам нужно сделать, это проверить операнды перед вычислением, чтобы убедиться, что это правильное выражение.

gcc говорит вам, в чем проблема с предупреждением:

main.c:5:3: warning: left shift count >= width of type [enabled by default]

Ваши изменения должны быть меньше размера шрифта, в противном случае это неопределенное поведение. Раздел проекта стандарта C99 6.5.7 Битовый оператор сдвига параграф 3 гласит:

[...] Если значение правого операнда отрицательно или больше или равно ширине повышенного левого операнда, поведение не определено.

Почему первый printf отличается от второго? Если вы строите, используя -fdump-tree-original вы увидите следующий код, сгенерированный для foo:

printf ((const char * restrict) "1<<32:%d\n", 0);
printf ((const char * restrict) "1<<(32-n):%d\n", 1 << 32 - n);

Кажется, это первый случай оптимизации 0 что согласуется с неопределенным поведением, компилятор может делать все, что угодно, включая поведение, которое работает.

Не удивляйся. вы имеете дело с 32-битным int, поэтому, когда вы делаете 1<<32, вы смещаете этот установленный бит сразу после конца int и обнуляете все это.

например, в двоичном коде:

    33222222 22211111 11111000 00000000
    10987654 32109876 54321098 76543210
 1: 00000000 00000000 00000000 00000001

   ^--- position #32

Я наконец-то нашел обходное решение, по крайней мере, чтобы сделать вывод идентичным.

#include <stdio.h>
#include <string.h>

void foo(int n){
  printf("1<<32:%d\n", 1<<32);
  printf("1<<(32-n):%d\n", (1<<(31-n))<<1);
}

int main(){
    foo(0);
}
Другие вопросы по тегам