2014-10-12 9 views
28

We wszystkich wersjach C i C++, przed 2014 pisaniaDlaczego 1 << 31 zmieniono tak, aby był definiowany przez implementację w C++ 14?

1 << (CHAR_BIT * sizeof(int) - 1) 

spowodowane nieokreśloną problem, ponieważ lewy biegów jest określona jako równoważne kolejnych mnożenie przez 2, a przesunięcie to powoduje ze znakiem przepełnienia:

Wynik E1 << E2 jest E1 przesunięty w lewo E2 pozycje bitowe; puste bity są wypełnione zerami. [...] Jeśli E1 ma podpisany typ i wartość nieujemną, i E1 × 2 E2 jest reprezentowalne w typie wyniku, to jest wynikową wartością; inaczej, zachowanie jest niezdefiniowane.

Jednak w C++ 14 tekst zmienił się << ale nie do mnożenia:

Wartość E1 << E2 jest E1 lewo przesunięty E2 pozycje bitów; wolne bity są wypełnione zerami. [...] Poza tym, jeśli ma E1 podpisaną rodzaj i wartości nieujemne i E1 x 2 E2 się przedstawić w odpowiadające unsigned typu wynik, to wartość przekształca się typ wyniku, jest wartością wynikową; w przeciwnym razie zachowanie jest niezdefiniowane.

zachowanie jest takie same, jak dla wykorzystania zakresem przypisania podpisanych typu, czyli objęte [conv.integral]/3:

Jeżeli typ docelowy jest podpisaniu wartość jest niezmieniona, jeśli może być reprezentowana w typie docelowym (i szerokości pola bitowego); w przeciwnym razie, wartość jest zdefiniowana przez implementację.

Oznacza to, że nadal nie jest przenośny, aby napisać 1 << 31 (w systemie z 32-bitową wersją). Dlaczego ta zmiana została wprowadzona w C++ 14?

+1

+1 Howard Hinnant [komentarze na ten temat tutaj] (http://stackoverflow.com/questions/19593938/is-left-shifting-a-negative-integer-undefined-behavior-in-c11#comment29091986_19593938), jedynym powodem, dla którego pamiętam ten komentarz jest to, że komentarz był częścią mojej inspiracji dla tego [pytania] (http://stackoverflow.com/q/21319413/1708801). –

+0

Wyobraź sobie, że zamiast tego po prostu napisali coś w stylu: "Ograniczenia: po awansie, lewy operand E1 będzie liczbą całkowitą bez znaku". To ocaliło ludzkość od astronomicznych ilości subtelnych błędów związanych z przesunięciem. – Lundin

Odpowiedz

16

istotną kwestią jest CWG 1457, gdzie uzasadnienie jest, że zmiana pozwala 1 << 31 być stosowane w stałych wyrażeniach:

Obecne brzmienie 5,8 [expr.shift] pkt 2 sprawia, że ​​zachowanie się niezdefiniowany utwórz najbardziej ujemną liczbę całkowitą danego typu przez przesuwając lewą stronę (podpis) 1 do bitu znaku, nawet jeśli nie jest to niezaprzeczalnie wykonane i działa poprawnie w większości architektur (dwójki-dopełnienie):

... jeżeli E1 ma podpisany typ i wartość nieujemną, a E1 * 2 E2 jest reprezentowana w typie wyniku, to jest wynikowa wartość; w przeciwnym razie zachowanie jest niezdefiniowane.

W rezultacie, ta technika nie mogą być stosowane w stałej ekspresji, co złamie dużej ilości kodu.

Wyrażenia stałe nie mogą zawierać niezdefiniowanych zachowań, co oznacza, że ​​użycie wyrażenia zawierającego UB w kontekście wymagającym stałego wyrażenia powoduje, że program jest źle sformułowany. libstdC++ 's numeric_limits::min, na przykład, once failed to compile in clang z tego powodu.

+0

Pisarz tego słowa wydaje się zdezorientowany ... nowa reguła nie pozwala na "stworzenie najbardziej ujemnej liczby całkowitej danego typu przez przesunięcie lewej strony (podpis) 1 do bitu znaku". Rezultat jest zdefiniowany przez implementację, co jest dalekie od zagwarantowania, że ​​da najbardziej negatywną wartość. –

+0

@BenVoigt Dla uzupełnienia dwójki istnieje tylko jeden zdrowy wynik. Jest to konsekwentnie wdrażane we wszystkich odpowiednich i powszechnych kompilatorach. – Columbo

+2

@Loopunroller: Ostatni standardowy cytat w pytaniu jest sformułowany w ten sposób, aby uniknąć wymagania konwersji wartości bez znaku na podpisane zasady przestrzegania dwóch elementów uzupełniających. Ponadto nie jest prawdą, że dla uzupełnienia dwójki istnieje tylko jeden zdrowy wynik. Arytmetyka nasycająca jest również użytecznym, rozsądnym wyborem. Można nawet powiedzieć, że jest to bardziej rozsądne niż podwojenie wartości dodatniej, tworząc negatyw. –

Powiązane problemy