Вопрос по c++, x86, c – Сдвигает ли 32-разрядное целое число uint64_t на компьютере x86 с неопределенным поведением?

11

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

Не должно быть. Какой компилятор вы используете? Jonathan Grynspan
Пожалуйста, уточни, что ты имеешь в виду под "м / с"? Stephen Canon
Вы бы получили больше ясности в том, что на самом деле здесь произошло, если бы вы разместили код, который плохо себя вел для вас. Steve Townsend
Вы, наверное, использовали что-то вродеuint64_t x = 1 << 33 и теперь обвинять компилятор (который, вероятно, предупредил бы вас, если бы вы включили предупреждения) Gunther Piez
Я никогда не слышал о "м / с" раньше. В моей ветке MC, скорее всего, будет означать микроконтроллер или, что менее вероятно, интегральную схему Motorola / Freescale. Представьте себе, насколько проще было бы работать программистом, если бы никто не использовал странные аббревиатуры! Lundin

Ваш Ответ

5   ответов
21

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

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

5 Результатом E1 >> E2 являются E1-сдвинутые вправо битовые позиции E2. Если 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, в зависимости от того, что делает аппаратное обеспечение (и при условии, что оценка компилятора во время компиляции эмулирует аппаратную семантику, а не носовые демоны.)

Используйте1ULL < 63 чтобы сделать операнд сдвигаunsigned long long д смена

@ drhirsch указал, что, вероятно, проблема: что-то вродеuint64_t x = 1 << 33 bames53
+ 1. Так и должно быть. Соответствующий компилятор должен соответствовать стандарту ArjunShankar
4

Даниэль Фишер ответ отвечает на вопрос о спецификации языка Си. Что касается того, что на самом деле происходит на компьютере с архитектурой x86, когда вы выполняете сдвиг на переменную величину, обратитесь к Руководство разработчика программного обеспечения Том 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 инструкция, это будет иметь определенное поведение на этой архитектуре. Но вы все равно должны избегать написания такого рода кода, который зависит от особенностей архитектуры.

Но так как это неопределенное поведение, компилятор мог бы решить, что фактический сдвиг не может произойти, и поэтому, возможно, никакая инструкция на ассемблере даже не будет отправлена. Так что не имеет смысла смотреть дальше на то, что будет делать ассемблер. hmijail
Компилятор будет использоватьshr для беззнаковых сдвигов вправо.sar ( Арифметика сдвиг вправо) будет дублировать MSB, нарушая семантику C для случаев, которые не являются UB. Peter Cordes
Компиляторы знают, что сдвиги маскируют счет до&31 или&63, так что на самом деле будет оптимизироватьvalue >> (count&31) одномуshr илиshrx инструкция, потому что она реализует&31 а также смена. Peter Cordes
4

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

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

#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 Операции побитового сдвига

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

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

1

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

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

Большинство компиляторов делают логические (вставка 0) правые сдвиги наunsigned и арифметическое (вставьте знаковый бит) правое смещение наsigned переменные. По крайней мере, любой компилятор, который я когда-либо использовал. Gunther Piez
@ JamesKanze C99 6.5.7: 4 «в противном случае поведение не определено». Если вы ищете статический анализатор, который (опционально) предупредит вас, если вы сдвинете отрицательное число влево, см. Ссылку в моей биографии. Pascal Cuoq
мещение левого отрицательного числа не является неопределенным поведением; это реализация определена. На практике, если у процессора есть инструкция, которая будет подписывать расширение при сдвиге влево, я ожидаю, что компилятор будет его использовать; «определенный реализацией» - это поддержка процессоров, у которых нет такой инструкции. James Kanze
@ drhirsch Я пояснил, что имел в виду «распространение знака» только для подписанных типов. Pascal Cuoq
@ JamesKanze The-val-left-shift-negative-alarms опция статического анализатора, о которой я говорил, оказывается включенной по умолчанию. Тем не менее, нам нужно было сделать это предупреждение необязательным, потому что многие программисты думают, что сдвиг отрицательных чисел влево определяется реализацией, и пока компиляторы соглашаются с ними, такие предупреждения являются для них только шумом. Pascal Cuoq

Похожие вопросы