Побитовое смещение (<<) странное поведение
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);
}