Oto funkcja C, która dodaje int
do drugiej funkcję, która zawiedzie , jeśli nastąpi przepełnienie:
int safe_add(int *value, int delta) {
if (*value >= 0) {
if (delta > INT_MAX - *value) {
return -1;
}
} else {
if (delta < INT_MIN - *value) {
return -1;
}
}
*value += delta;
return 0;
}
Niestety nie jest dobrze zoptymalizowany przez GCC lub Clang:
safe_add(int*, int):
movl (%rdi), %eax
testl %eax, %eax
js .L2
movl $2147483647, %edx
subl %eax, %edx
cmpl %esi, %edx
jl .L6
.L4:
addl %esi, %eax
movl %eax, (%rdi)
xorl %eax, %eax
ret
.L2:
movl $-2147483648, %edx
subl %eax, %edx
cmpl %esi, %edx
jle .L4
.L6:
movl $-1, %eax
ret
Ta wersja z __builtin_add_overflow()
int safe_add(int *value, int delta) {
int result;
if (__builtin_add_overflow(*value, delta, &result)) {
return -1;
} else {
*value = result;
return 0;
}
}
jest lepiej zoptymalizowany :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
ale jestem ciekawy, czy istnieje sposób bez użycia wbudowanych, które zostaną dopasowane do wzorca przez GCC lub Clanga.
c
gcc
optimization
clang
integer-overflow
Tavian Barnes
źródło
źródło
Odpowiedzi:
Najlepszym, jaki wymyśliłem, jeśli nie masz dostępu do flagi przepełnienia architektury, jest robienie rzeczy
unsigned
. Pomyśl o całej arytmetyce bitów, ponieważ interesuje nas tylko najwyższy bit, który jest bitem znaku interpretowanym jako wartości podpisane.(Wszystkie te błędy podpisywania modulo, nie sprawdziłem tego dokładnie, ale mam nadzieję, że pomysł jest jasny)
Jeśli znajdziesz wersję dodatku wolną od UB, taką jak atomowa, asembler jest nawet bez rozgałęzienia (ale z prefiksem blokady)
Gdybyśmy mieli taką operację, ale jeszcze bardziej „zrelaksowaną”, mogłoby to jeszcze bardziej poprawić sytuację.
Take3: Jeśli użyjemy specjalnego „rzutowania” z niepodpisanego wyniku na podpisany, teraz jest on wolny od gałęzi:
źródło
unsigned
. Zależy to jednak od tego, że w typie niepodpisanym maskowany jest nie tylko bit znaku. (Obie są teraz gwarantowane w C2x, to znaczy, trzymaj wszystkie łuki, które mogliśmy znaleźć). Następnie nie można rzutowaćunsigned
wyniku z powrotem, jeśli jest on większy niżINT_MAX
, byłoby to zdefiniowane w implementacji i może podnieść sygnał.Sytuacja z podpisanymi operacjami jest znacznie gorsza niż z niepodpisanymi i widzę tylko jeden wzorzec dla podpisanego dodawania, tylko dla clang i tylko wtedy, gdy dostępny jest szerszy typ:
clang daje dokładnie taki sam asm jak w przypadku __builtin_add_overflow:
W przeciwnym razie najprostszym rozwiązaniem, jakie mogę wymyślić, jest to (z interfejsem używanym przez Jensa):
gcc i clang generują bardzo podobny asm . gcc daje to:
Chcemy obliczyć sumę
unsigned
, więcunsigned
musimy być w stanie przedstawić wszystkie wartościint
bez trzymania się jednej z nich. Aby łatwo przekonwertować wynik zunsigned
naint
, przydatne jest również przeciwieństwo. Ogólnie zakłada się uzupełnienie dwóch.Na wszystkich popularnych platformach myślę, że możemy przekonwertować z
unsigned
naint
zwykłe zadanie,int sum = u;
ale, jak wspomniał Jens, nawet najnowszy wariant standardu C2x pozwala na podniesienie sygnału. Kolejnym najbardziej naturalnym sposobem jest zrobienie czegoś takiego:*(unsigned *)&sum = u;
ale warianty wypełnienia bez pułapki najwyraźniej mogą się różnić dla typów podpisanych i niepodpisanych. Powyższy przykład jest trudny. Na szczęście zarówno gcc, jak i clang optymalizują tę trudną konwersję.PS Dwa powyższe warianty nie mogły być bezpośrednio porównane, ponieważ mają różne zachowanie. Pierwszy podąża za pierwotnym pytaniem i nie blokuje się
*value
w przypadku przepełnienia. Drugi podąża za odpowiedzią Jensa i zawsze blokuje zmienną wskazywaną przez pierwszy parametr, ale nie rozgałęzia się.źródło
najlepsza wersja, jaką mogę wymyślić, to:
który produkuje:
źródło
int
, rzutowanie z szerszego typu spowoduje albo wygenerowanie wartości zdefiniowanej przez implementację, albo podniesienie sygnału. Wszystkie implementacje, które dbam o to, aby to zdefiniować, aby zachować wzór bitów, który działa prawidłowo.Mógłbym zmusić kompilator do używania flagi znaku, zakładając (i potwierdzając) reprezentację dopełniacza dwóch bez wypełniania bajtów. Takie wdrożenia powinny dać wymagane zachowanie w wierszu opatrzonym komentarzem, chociaż nie mogę znaleźć pozytywnego formalnego potwierdzenia tego wymogu w standardzie (i prawdopodobnie nie ma takiego).
Zauważ, że poniższy kod obsługuje tylko dodatnie dodawanie liczb całkowitych, ale można je rozszerzyć.
Daje to zarówno clang, jak i GCC:
źródło
_Static_assert
cel nie ma większego sensu, ponieważ jest to trywialnie prawdziwe w każdej obecnej architekturze, a nawet zostanie narzucone dla C2x.INT_MAX
. Zmienię post. Ale z drugiej strony nie sądzę, aby ten kod i tak był wykorzystywany w praktyce.