Niepisane przepełnienie liczb całkowitych jest dobrze zdefiniowane zarówno przez standardy C, jak i C ++. Na przykład stwierdza stan C99 standard ( §6.2.5/9
)
Obliczenia obejmujące niepodpisane operandy nigdy nie mogą się przepełnić, ponieważ wynik, który nie może być reprezentowany przez wynikowy typ liczb całkowitych bez znaku, jest zmniejszany modulo o liczbę, która jest o jeden większa od największej wartości, jaką może reprezentować wynikowy typ.
Jednak oba standardy stwierdzają, że podpisane przepełnienie liczb całkowitych jest zachowaniem niezdefiniowanym. Ponownie, ze standardu C99 ( §3.4.3/1
)
Przykładem nieokreślonego zachowania jest zachowanie przy przepełnieniu liczby całkowitej
Czy istnieje jakaś historyczna lub (jeszcze lepsza!) Techniczna przyczyna tej rozbieżności?
źródło
if (a + b < a)
). Przepełnienie przy mnożeniu jest trudne zarówno dla typów podpisanych, jak i niepodpisanych.MAX_INT+1 == -0
, podczas gdy na uzupełnieniu dwójki byłbyINT_MIN
Odpowiedzi:
Historyczny powód jest taki, że większość implementacji C (kompilatorów) właśnie używała takiego zachowania przepełnienia, które było najłatwiejsze do wdrożenia z zastosowaną reprezentacją liczb całkowitych. Implementacje w języku C zwykle używały tej samej reprezentacji, z której korzysta procesor - więc zachowanie związane z przepełnieniem wynikało z reprezentacji liczb całkowitych używanych przez procesor.
W praktyce tylko reprezentacje podpisanych wartości mogą się różnić w zależności od implementacji: uzupełnienie własne, uzupełnienie dwóch, wielkość znaku. Dla typu bez znaku nie ma powodu, aby standard zezwalał na zmiany, ponieważ istnieje tylko jedna oczywista reprezentacja binarna (standard zezwala tylko na reprezentację binarną).
Odpowiednie cytaty:
C99 6.2.6.1:3 :
C99 6.2.6.2:2 :
Obecnie wszystkie procesory używają reprezentacji uzupełnienia do dwóch, ale podpisane przepełnienie arytmetyczne pozostaje niezdefiniowane, a twórcy kompilatorów chcą, aby pozostała niezdefiniowana, ponieważ używają tej niezdefiniowanej pomocy do optymalizacji. Zobacz na przykład ten post na blogu Iana Lance'a Taylora lub skargę Agnera Foga oraz odpowiedzi na jego raport o błędzie.
źródło
Oprócz dobrej odpowiedzi Pascala (jestem pewien, że to główna motywacja), możliwe jest również, że niektóre procesory powodują wyjątek w przypadku przepełnienia liczby całkowitej ze znakiem, co oczywiście spowodowałoby problemy, gdyby kompilator musiał „zorganizować inne zachowanie” ( np. użyj dodatkowych instrukcji, aby sprawdzić potencjalne przepełnienie i w takim przypadku obliczyć inaczej).
Warto również zauważyć, że „niezdefiniowane zachowanie” nie oznacza „nie działa”. Oznacza to, że wdrożenie może robić, co chce w tej sytuacji. Obejmuje to robienie „właściwych rzeczy”, a także „wezwania policji” lub „rozbicia się”. Większość kompilatorów, jeśli to możliwe, wybiera „rób to, co należy”, zakładając, że jest to stosunkowo łatwe do zdefiniowania (w tym przypadku tak jest). Jeśli jednak występują przepełnienia w obliczeniach, ważne jest, aby zrozumieć, co tak naprawdę powoduje, i że kompilator MOŻE zrobić coś innego niż się spodziewasz (i że może to bardzo zależeć od wersji kompilatora, ustawień optymalizacji itp.) .
źródło
int f(int x) { return x+1>x; }
z optymalizacją. GCC i ICC optymalizują powyższe opcje przy użyciu domyślnych opcjireturn 1;
.int
przepełnienia w zależności od poziomów optymalizacji, zobacz ideone.com/cki8nM Myślę, że to pokazuje, że twoja odpowiedź zawiera złe porady.Przede wszystkim należy pamiętać, że C11 3.4.3, podobnie jak wszystkie przykłady i nuty, nie jest tekstem normatywnym, a zatem nie ma sensu cytować!
Odpowiedni tekst stwierdzający, że przepełnienie liczb całkowitych i liczb zmiennoprzecinkowych jest niezdefiniowanym zachowaniem, jest następujący:
C11 6,5 / 5
Wyjaśnienie dotyczące zachowania niepodpisanych typów liczb całkowitych można znaleźć tutaj:
C11 6.2.5 / 9
To sprawia, że typy całkowite bez znaku są specjalnym przypadkiem.
Należy również pamiętać, że istnieje wyjątek, jeśli dowolny typ jest konwertowany na typ podpisany, a starej wartości nie można już reprezentować. Zachowanie jest wówczas jedynie definiowane w ramach implementacji, chociaż sygnał może zostać podniesiony.
C11 6.3.1.3
źródło
Oprócz innych wspomnianych problemów, posiadanie niepodpisanego zawijania matematycznego powoduje, że niepodpisane typy liczb całkowitych zachowują się jak abstrakcyjne grupy algebraiczne (co oznacza, że między innymi dla dowolnej pary wartości
X
iY
będą istniały inne wartościZ
, któreX+Z
, jeśli zostaną poprawnie rzutowane , równaY
iY-Z
będzie, jeśli odpowiednio rzucona, równaX
). Jeśli niepodpisane wartości były jedynie typami lokalizacji do przechowywania, a nie typami wyrażeń pośrednich (np. Jeśli nie było żadnego niepodpisanego odpowiednika największego typu liczby całkowitej, a operacje arytmetyczne na niepodpisanych typach zachowywały się tak, jakby były najpierw konwertowane na większe typy ze znakiem, wówczas nie byłoby takiej potrzeby zdefiniowanego zachowania zawijania, ale trudno jest wykonać obliczenia w typie, który nie ma np. odwrotności addytywnej.Pomaga to w sytuacjach, gdy zachowanie zawijania jest rzeczywiście przydatne - na przykład przy numerach sekwencji TCP lub niektórych algorytmach, takich jak obliczanie wartości skrótu. Może to również pomóc w sytuacjach, w których konieczne jest wykrycie przepełnienia, ponieważ wykonywanie obliczeń i sprawdzenie, czy przepełnienie jest często łatwiejsze niż wcześniejsze sprawdzenie, czy przepełnią, szczególnie jeśli obliczenia dotyczą największej dostępnej liczby całkowitej.
źródło
a+b-c
jest obliczane w pętli, aleb
ic
są stałe wewnątrz tej pętli, może to być pomocne, aby przenieść obliczenia(b-c)
zewnątrz pętli, ale robi to wymagałoby wśród innych rzeczy, które(b-c)
dają wartość, która po dodaniu doa
, przyniesiea+b-c
, co z kolei wymagac
odwrotności dodatku.(a+b)-c
jest równy,a+(b-c)
czy wartość arytmetycznab-c
jest reprezentowalna w obrębie typu, podstawienie będzie ważne bez względu na możliwy zakres wartości dla(b-c)
.Być może innym powodem, dla którego zdefiniowano arytmetykę bez znaku, jest to, że liczby bez znaku tworzą liczby całkowite modulo 2 ^ n, gdzie n jest szerokością liczby bez znaku. Numery niepodpisane są po prostu liczbami całkowitymi reprezentowanymi za pomocą cyfr binarnych zamiast cyfr dziesiętnych. Wykonywanie standardowych operacji w systemie modułowym jest dobrze zrozumiałe.
Cytat PO odnosi się do tego faktu, ale także podkreśla fakt, że istnieje tylko jeden, jednoznaczny, logiczny sposób reprezentowania liczb całkowitych bez znaku w systemie binarnym. Natomiast liczby podpisane są najczęściej reprezentowane za pomocą uzupełnienia do dwóch, ale możliwe są inne wybory, jak opisano w normie (sekcja 6.2.6.2).
Reprezentacja uzupełnienia Two pozwala niektórym operacjom na bardziej sensowny w formacie binarnym. Np. Inkrementacja liczb ujemnych jest taka sama jak dla liczb dodatnich (należy się spodziewać w warunkach przepełnienia). Niektóre operacje na poziomie komputera mogą być takie same dla numerów podpisanych i niepodpisanych. Jednak przy interpretacji wyników tych operacji niektóre przypadki nie mają sensu - przepełnienie dodatnie i ujemne. Ponadto wyniki przepełnienia różnią się w zależności od podpisanej reprezentacji.
źródło