2010-10-16 17 views
14

Istnieje wiele pytań dotyczących wykrywania przekroczenia liczby całkowitej PRZED faktycznym dodawaniem/odejmowaniem ze względu na możliwe undefined behavior. Więc moje pytanie brzmi:Przepełnienie całkowite i niezdefiniowane zachowanie

Po pierwsze, dlaczego wyprodukuje to undefined behavior?

można myśleć 2 powoduje:

1) procesora, który powoduje wyjątek w tym przypadku. Oczywiście, można go wyłączyć i najprawdopodobniej zrobi to dobrze napisany CRT.

2) Procesor wykorzystujący inne reprezentacje binarne liczb (uzupełnienie 1 - podstawa 10?). W takim przypadku niezdefiniowane zachowanie przejawia się jako inny wynik (ale nie ulegnie awarii!). Cóż, moglibyśmy z tym żyć.

Dlaczego więc ktoś powinien unikać powodowania? Czy czegoś brakuje?

+1

Obowiązkowe łącze: http://blog.regehr.org/archives/213 –

+0

powiązane: http://stackoverflow.com/questions/18195715/why-is-unsigned-integer-overflow-defined-behavior-but- signed-integer-overflow-is –

Odpowiedz

11

Chociaż większość nowoczesne procesory używać 2 za uzupełnienie, a wyniki całkowitą przepełnienia przewidywalny modulo przejściem cyklicznym, to bynajmniej nie jest uniwersalny - trzymać język na tyle ogólne, że może on być stosowany w szerokim zakresie architektur lepiej określ, że przepełnienie całkowite to UB.

+1

Dowolny procesor jest dwójką uzupełnienia, jeśli twój kompilator po prostu nie generuje niepotrzebnych, podpisanych kodów arytmetycznych i używa tych bez znaku dla typów podpisanych i niepodpisanych. Może to powodować mniejsze straty wydajności podczas dokonywania porównań (na przykład teraz 'x <-1' wymaga 2 porównań na poziomie maszyny zamiast tylko 1), ale ja wciąż wolałbym to w ten sposób. –

+0

@R: Uzupełnienie 2: nie jest jedynym problemem - np. niektóre procesory (na przykład DSP) mają nasycającą arytmetykę, a nie arytmetyczną modulo. –

+1

Arytmetyka modułowa jest wymagana do obsługi typów niepodpisanych ... Jeśli twoja maszyna jej nie posiada, domyślam się, że musisz ją zaimplementować. Jednym podejściem może być użycie górnego bitu jako dopełnienia i wyzerowanie go po każdej operacji. –

4

Bity w specyfikacji dotyczą pewnej optymalizacji kompilatora. Na przykład:

if (a > 0 && b > 0) { 
    if (a + b <= 0) { 
     // this branch may be optimized out by compiler 
    } else { 
     // this branch will always run 
    } 
} 

Współczesne kompilatory C nie są takie proste, wiele zgadywania i optymalizacji.

+0

Dobrze, ale to tylko oznacza, że ​​kompilator C nie pomoże ci go wykryć. – ruslik

+0

inny punkt: podczas gdy kompilator byłby w stanie wykryć ten stan arytmetyczny, niektóre operacje bitowe byłyby w stanie oszukać go, aby warunek nie został zoptymalizowany. Ponadto, jak sugeruje Nataose, możemy po prostu zadeklarować sumę jako "lotną". – ruslik

+3

To nie jest prawidłowe użycie słowa kluczowego 'volatile'. Jeśli to działa, jest wynikiem konkretnej implementacji, a nie określonego zachowania. To znaczy. to wciąż niezdefiniowane zachowanie. –

0

Myślę, że twoje założenie 1), że można to wyłączyć dla danego procesora było fałszywe na co najmniej jednej ważnej historycznej architektury, CDC, jeśli moja pamięć jest poprawna.

+0

Ten jest naprawdę stary. Wolałbym obsługiwać te przypadki z '# ifdef'. – ruslik

+0

@ruslik: Pytanie nie jest ** jak ** poradzić sobie z tym, '# ifdef' jest tylko jednym ze sposobów uniknięcia UB. Ale widzisz, jak sam to mówisz, poradzisz sobie z tym w jakiś sposób. –

+0

kompatybilność wsteczna ma swoje ograniczenia. W tym przypadku wolałbym pisać błędny, ale prosty kod. – ruslik

11

Podczas gdy historyczne podpisane przepełnienie zostało określone, ponieważ niezdefiniowane zachowanie było prawdopodobnie tymi fałszywymi reprezentacjami starszego typu (uzupełnieniem/wielkością znaku) i przerywanymi przerwaniami, współczesnym powodem pozostawania niezdefiniowanym zachowaniem jest optymalizacja. Jak zauważyła Ji-16 SDiZ, fakt, że podpisane przepełnienie jest niezdefiniowanym zachowaniem, pozwala kompilatorowi zoptymalizować niektóre czynniki warunkowe, których prawda algebraiczna (ale niekoniecznie reprezentująca prawdę na poziomie reprezentacji) jest już ustalona przez poprzednią gałąź. Może również umożliwić kompilatorowi uprościć algebraicznie niektóre wyrażenia (szczególnie te wymagające mnożenia lub dzielenia) w sposób, który mógłby dać inne wyniki niż pierwotnie napisana kolejność oceny, jeśli podwyrażenie zawiera przepełnienie, ponieważ kompilator może założyć, że przepełnienie nie dzieje się z operandami, które mu dałeś.

Innym ogromnym przykładem niezdefiniowanego zachowania w celu umożliwienia optymalizacji są reguły aliasingu.

+3

dern histeryczne rodzynki –

+0

Można argumentować, że liczby całkowite bez znaku powinny zachowywać się konsekwentnie, ponieważ dlaczego nie założyć, że a + b> = a i a + b> = b dla liczb całkowitych bez znaku w optymalizatorze (podobnie jak przy kończeniu a + b> 0 dla > 0 i b> 0 w przykładzie Ji 16 SDiZ dla liczb całkowitych ze znakiem)? W moich oczach jest to bardziej osobliwość w specyfikacji C. – nucleon

+1

@nucleon: Niepodpisane typy są * określone jako arytmetyka modularna * (gdzie algebraiczne równoważności nierówności nie posiadają). Podpisane typy nie są. Jeśli chcesz nazwać to dziwactwem w C, niech tak będzie, ale tak właśnie jest i nie jest to coś, co możesz zmienić. –

Powiązane problemy