To jest UB; w terminologii ISO C ++ całe zachowanie całego programu jest całkowicie nieokreślone dla wykonania, które ostatecznie uderza w UB. Klasycznym przykładem jest to, o ile chodzi o standard C ++, może sprawić, że demony wylecą z twojego nosa. (Odradzam stosowanie implementacji, w której demony nosowe są realną możliwością). Zobacz inne odpowiedzi, aby uzyskać więcej informacji.
Kompilatory mogą „powodować problemy” w czasie kompilacji dla ścieżek wykonywania, które widzą, co prowadzi do UB widocznego w czasie kompilacji, np. Zakładając, że te podstawowe bloki nigdy nie zostaną osiągnięte.
Zobacz także Co każdy programista C powinien wiedzieć o nieokreślonym zachowaniu (blog LLVM). Jak wyjaśniono tam, przepełnienie sygnatury UB pozwala kompilatorom udowodnić, że for(... i <= n ...)
pętle nie są pętlami nieskończonymi, nawet dla nieznanych n
. Pozwala im także „promować” liczniki pętli int do szerokości wskaźnika zamiast powtarzania rozszerzenia znaku. (Tak więc konsekwencją UB w takim przypadku może być dostęp poza niskimi elementami 64k lub 4G tablicy, jeśli spodziewałeś się podpisanego zawijania i
w jego zakresie wartości.)
W niektórych przypadkach kompilatory będą wysyłały niedozwolone instrukcje, takie jak x86, ud2
dla bloku, który prawdopodobnie spowoduje UB, jeśli kiedykolwiek zostanie wykonany. (Należy pamiętać, że funkcja może nigdy nie zostać wywołana, więc kompilatory nie mogą generalnie szaleć i łamać innych funkcji, a nawet możliwych ścieżek przez funkcję, która nie uderza w UB, tj. Kod maszynowy, do którego kompiluje, musi nadal działać dla wszystkie dane wejściowe, które nie prowadzą do UB.)
Prawdopodobnie najbardziej wydajnym rozwiązaniem jest ręczne oderwanie ostatniej iteracji, aby factor*=10
uniknąć niepotrzebnych .
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
Lub jeśli ciało pętli jest duże, rozważ użycie po prostu typu bez znaku dla factor
. Następnie możesz pozwolić, aby niepodpisane zwielokrotniło się, a po prostu wykona dobrze zdefiniowane zawijanie do pewnej potęgi 2 (liczba bitów wartości w typie bez znaku).
Jest to w porządku, nawet jeśli używasz go z podpisanymi typami, szczególnie jeśli konwersja niepodpisana-> podpisana nigdy się nie przepełnia.
Konwersja między niepodpisanym a 2-znakowym uzupełnieniem jest bezpłatna (taki sam wzór bitowy dla wszystkich wartości); zawijanie modulo dla int -> unsigned określone przez standard C ++ upraszcza po prostu użycie tego samego wzorca bitowego, w przeciwieństwie do uzupełnienia lub znaku / wielkości.
I niepodpisany-> podpisany jest podobnie trywialny, chociaż jest zdefiniowany implementacyjnie dla wartości większych niż INT_MAX
. Jeśli nie używasz ogromnego niepodpisanego wyniku z ostatniej iteracji, nie masz się czym martwić. Ale jeśli tak, zobacz Czy konwersja z niepodpisanego do podpisanego jest niezdefiniowana? . Przypadek „nie pasuje” jest zdefiniowany w implementacji , co oznacza, że implementacja musi wybrać pewne zachowanie; rozsądne po prostu obcinają (jeśli to konieczne) niepodpisany wzorzec bitów i używają go jako podpisanego, ponieważ działa to dla wartości w zakresie w ten sam sposób bez dodatkowej pracy. I zdecydowanie nie jest to UB. Tak duże wartości bez znaku mogą stać się liczbami całkowitymi ze znakiem ujemnym. np. po int x = u;
gcc i clang nie optymalizujx>=0
jak zawsze są prawdziwe, nawet bez -fwrapv
, ponieważ określają zachowanie.