2012-05-08 2 views
11

Изучив трудный путь, я попытался сдвинуть влево long long и uint64_t на более чем 32 бит на машине x86, полученной 0. Я смутно помню, что где-то читал, а на 32-битных машинных операциях сдвига работают только первые 32 бита, но не могут вспомнить источник. Я хотел бы знать, может быть, если Shifting более 32 бит целого числа uint64_t на машине x86 является неопределенным поведением?Перемещает более 32 бит целого числа uint64_t на машине x86 Неопределенное поведение?

+3

Не должно быть. Какой компилятор вы используете? –

+0

Вы помните, сколько бит вы точно пытались сдвинуть? – RedX

+2

Просьба уточнить, что вы подразумеваете под «m/c»? –

ответ

20

Стандарт говорит (6.5.7 в n1570):

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

4 Результат E1 < < E2 - левое положение E2, расположенное слева; освобожденные биты заполняются нулями . Если E1 имеет неподписанный тип, значение результата E1 × 2 E2, приведенное по модулю больше, чем максимальное значение, представляемое в типе результата. Если E1 имеет подписанный тип и неотрицательное значение, а E1 × 2 E2 представляется в виде результата, то это полученное значение; в противном случае поведение не определено.

5 Результат E1 >> E2 - это правые позиции E2 в E1. Если E1 имеет неподписанный тип или если E1 имеет подписанный тип и неотрицательное значение, то результатом будет интеграл часть частного E1/2 E2. Если E1 имеет подписанный тип и отрицательное значение, итоговое значение определено в соответствии с реализацией.

Смещение uint64_t расстояние от 64 бит полностью определяется стандартом.

С long long должно быть не менее 64 бит, смещение long long значения менее 64 бит определяются стандартом для неотрицательных значений, если результат не переполняется.

Обратите внимание, что если вы напишете литерал, который вписывается в 32 бита, например. uint64_t s = 1 << 32, как предполагалось, @drhirsch, вы фактически не сдвигаете 64-битное значение, а 32-битное. Это неопределенное поведение. Наиболее распространенными результатами являются сдвиг на shift_distance % 32 или 0, в зависимости от того, что делает оборудование.

+0

+1. Так оно и должно быть. Соответствующий компилятор должен следовать стандарту C. – ArjunShankar

+1

@drhirsch указал, какова вероятная проблема: что-то вроде 'uint64_t x = 1 << 33' – bames53

1

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

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

+0

Большинство компиляторов делают логические (вставляют 0) сдвиги вправо на 'unsigned' и арифметические (вставляют знаковый бит) сдвиги вправо по переменным' signed'. По крайней мере, любой компилятор, который я когда-либо использовал. – hirschhornsalz

+0

Левое смещение отрицательного числа не является неопределенным поведением; это реализация определена. На практике, если процессор имеет команду, которая будет подписать расширение при сдвиге влево, я бы ожидал, что компилятор ее будет использовать; «определенная реализация» предназначена для поддержки процессоров, которые не имеют такой инструкции. –

+0

@JamesKanze C99 6.5.7: 4 "в противном случае поведение не определено". Если вы ищете статический анализатор, который (опционально) предупредит вас, если вы оставите сдвиг отрицательного числа, см. Ссылку в моей биографии. –

3

Стандарт C требует, чтобы смена работала правильно. У конкретного глючного компилятора может быть описанный вами дефект, но это нехорошее поведение.

Это тестовая программа:

#include <stdio.h> 
#include <inttypes.h> 

int main(void) 
{ 
    uint64_t x = 1; 
    for (int i = 0; i < 64; i++) 
     printf("%2d: 0x%.16" PRIX64 "\n", i, (x << i)); 
    return 0; 
} 

Это выход на i686 машине под управлением RHEL 5 с GCC 4.1.2, а также на x86/64 машины (также работает RHEL 5 и GCC 4.1. 2) и на x86/64 Mac (работает под управлением Mac OS X 10.7.3 с GCC 4.7.0). Поскольку это ожидаемый результат, я пришел к выводу, что на 32-битной машине нет необходимой проблемы, и что GCC по крайней мере не обнаружил такой ошибки с GCC 4.1.2 (и, вероятно, никогда не обнаружил такую ​​ошибку).

0: 0x0000000000000001 
1: 0x0000000000000002 
2: 0x0000000000000004 
3: 0x0000000000000008 
4: 0x0000000000000010 
5: 0x0000000000000020 
6: 0x0000000000000040 
7: 0x0000000000000080 
8: 0x0000000000000100 
9: 0x0000000000000200 
10: 0x0000000000000400 
11: 0x0000000000000800 
12: 0x0000000000001000 
13: 0x0000000000002000 
14: 0x0000000000004000 
15: 0x0000000000008000 
16: 0x0000000000010000 
17: 0x0000000000020000 
18: 0x0000000000040000 
19: 0x0000000000080000 
20: 0x0000000000100000 
21: 0x0000000000200000 
22: 0x0000000000400000 
23: 0x0000000000800000 
24: 0x0000000001000000 
25: 0x0000000002000000 
26: 0x0000000004000000 
27: 0x0000000008000000 
28: 0x0000000010000000 
29: 0x0000000020000000 
30: 0x0000000040000000 
31: 0x0000000080000000 
32: 0x0000000100000000 
33: 0x0000000200000000 
34: 0x0000000400000000 
35: 0x0000000800000000 
36: 0x0000001000000000 
37: 0x0000002000000000 
38: 0x0000004000000000 
39: 0x0000008000000000 
40: 0x0000010000000000 
41: 0x0000020000000000 
42: 0x0000040000000000 
43: 0x0000080000000000 
44: 0x0000100000000000 
45: 0x0000200000000000 
46: 0x0000400000000000 
47: 0x0000800000000000 
48: 0x0001000000000000 
49: 0x0002000000000000 
50: 0x0004000000000000 
51: 0x0008000000000000 
52: 0x0010000000000000 
53: 0x0020000000000000 
54: 0x0040000000000000 
55: 0x0080000000000000 
56: 0x0100000000000000 
57: 0x0200000000000000 
58: 0x0400000000000000 
59: 0x0800000000000000 
60: 0x1000000000000000 
61: 0x2000000000000000 
62: 0x4000000000000000 
63: 0x8000000000000000 
1

Нет, все в порядке.

ISO 9899: 2011 6.5.7 Битовые операторы сдвига

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

Это не тот случай, поэтому все хорошо и четко определено.

4

Daniel Fischer's answer отвечает на вопрос о спецификации языка C. Что касается того, что на самом деле происходит на машине x86 при выводе сдвига на переменную величину, обратитесь к разделу Intel Software Developer Manual Том 2B, стр. 4-506:

Количество маскируется до 5 бит (или 6 бит , если в 64-битном режиме и используется REX.W). Диапазон отсчетов ограничен 0 - 31 (или 63, если используется 64-битный режим и используется REX.W).

Так что если вы попытаетесь сдвинуть на величину больше 31 или 63 (для 32- и 64-разрядных значений соответственно), аппаратное обеспечение будет использовать только нижние 5 или 6 бит суммы сдвига. Так что этот код:

uint32_t RightShift(uint32_t value, uint32_t count) 
{ 
    return value >> count; 
} 

будет приводить к RightShift(2, 33) == 1 на x86 и x86-64. По-прежнему неопределенное поведение в соответствии со стандартом C, но на x86, если компилятор компилирует его до команды sar, он будет определять поведение этой архитектуры. Но вы все равно должны избегать написания такого кода, который зависит от специфики архитектуры.

+0

Но поскольку это неопределенное поведение, компилятор, возможно, решил, что фактический сдвиг не может произойти, и поэтому потенциально никакая инструкция ассемблера не будет даже излучать. Поэтому не имеет смысла смотреть дальше на то, что сделает ассемблер. – hmijail

Смежные вопросы