Да, поведение целого числа со знаком со сдвигом влево изменилось с C++20.
В C++17 сдвиг влево положительного целого числа со знаком в знаковый бит вызывает поведение определяемое реализацией.1 Пример:
int i = INT_MAX;
int j = i << 1; // implementation defined behavior with std < C++20
C++20 изменил это поведение на определенное, поскольку оно предписывает дополнение до двух представление целых чисел со знаком.2,3
В C++17 сдвиг отрицательного целого числа со знаком вызывает поведение undefined.1 Пример:
int i = -1;
int j = i << 1; // undefined behavior with std < C++20
В C++20 это также изменилось, и теперь эта операция также вызывает определенное поведение.3
Это кажется странным изменением. Не сместит ли это бит знака?
Да, знаковый сдвиг влево сдвигает бит знака. Пример:
int i = 1 << (sizeof(int)*8-1); // C++20: defined behavior, set most significant bit
int j = i << 1; // C++20: defined behavior, set to 0
Основная причина указания чего-либо как неопределенного или определяемого реализацией поведения заключается в том, чтобы обеспечить эффективную реализацию на другом оборудовании.
В настоящее время, поскольку все ЦП реализуют дополнение до двух, естественно, что стандарт C++ предписывает его. И если вы предписываете дополнение до двух, то только последует то, что вы сделаете вышеописанные операции определенным поведением, потому что это также то, как сдвиг влево ведет себя во всех архитектурах набора инструкций с дополнением до двух (ISA).
IOW, оставив его реализацию определенной и неопределенной, ничего вам не купит.
Или, если вам нравилось предыдущее неопределенное поведение, какое вам дело до того, изменится ли оно на определенное поведение? Вы по-прежнему можете избежать этой операции, как и раньше. Вам не пришлось бы менять свой код.
1
Значение E1 << E2
— это битовые позиции E1 со сдвигом влево E2; освободившиеся биты заполняются нулями. Если E1 имеет беззнаковый тип, значение результата равно E1 × 2**E2
, уменьшенному по модулю на единицу больше, чем максимальное значение, представленное в типе результата. В противном случае, если E1 имеет тип со знаком и неотрицательное значение, а E1 × 2**E2
представляется в соответствующем беззнаковом типе типа результата, то это значение преобразуется в результат тип — результирующее значение; иначе поведение не определено.
(Окончательная версия C++17 черновик, раздел 8.8 Операторы сдвига [expr.shift], параграф 2, стр. 132 — выделение мое)
2
[..] Для каждого значения x целочисленного типа со знаком значение соответствующего целочисленного типа без знака, конгруэнтное x по модулю 2 N, имеет то же значение соответствующих битов в его представлении значения. 41) Это также известно как представление с дополнением до двух. [..]
(C++20 последняя рабочая черновик, Раздел 6.8.1 Основные типы [basic.fundamental], Параграф 3, стр. 66)
3
Значение E1 << E2
– это уникальное значение, конгруэнтное E1 × 2**E2 modulo 2**N
, где N – ширина тип результата. [Примечание: E1 — это битовые позиции E2, сдвинутые влево; освободившиеся биты заполняются нулями. — примечание в конце]
(C++20 последняя рабочая черновик, раздел 7.6.7 Операторы сдвига [expr.shift], параграф 2, стр. 129, ссылка моя)
person
maxschlepzig
schedule
16.03.2020