Voici une fonction C qui ajoute un int
à un autre, échouant en cas de débordement:
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;
}
Malheureusement, il n'est pas bien optimisé par GCC ou 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
Cette version avec __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;
}
}
est mieux optimisé :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
mais je suis curieux de savoir s'il existe un moyen sans utiliser de modules intégrés qui seront mis en correspondance par GCC ou Clang.
c
gcc
optimization
clang
integer-overflow
Tavian Barnes
la source
la source
Réponses:
Le meilleur que j'ai trouvé, si vous n'avez pas accès au drapeau de débordement de l'architecture, c'est de faire les choses dans
unsigned
. Pensez simplement à toute l'arithmétique des bits ici en ce que nous ne sommes intéressés que par le bit le plus élevé, qui est le bit de signe lorsqu'il est interprété comme des valeurs signées.(Toutes ces erreurs de signature modulo, je n'ai pas vérifié cela à fond, mais j'espère que l'idée est claire)
Si vous trouvez une version de l'addition qui est exempte d'UB, comme une version atomique, l'assembleur est même sans branche (mais avec un préfixe de verrouillage)
Donc, si nous avions une telle opération, mais encore plus "détendue", cela pourrait encore améliorer la situation.
Take3: Si nous utilisons un "cast" spécial du résultat non signé au résultat signé, celui-ci est maintenant sans branche:
la source
unsigned
. Mais cela dépend du fait que le type non signé n'a pas seulement le bit de signe masqué. (Les deux sont maintenant garantis dans C2x, c'est-à-dire, valables pour toutes les arches que nous pourrions trouver). Ensuite, vous ne pouvez pasunsigned
restituer le résultat s'il est supérieur àINT_MAX
, cela serait défini par l'implémentation et pourrait déclencher un signal.La situation avec les opérations signées est bien pire qu'avec les opérations non signées, et je ne vois qu'un seul modèle pour l'addition signée, uniquement pour clang et uniquement lorsqu'un type plus large est disponible:
clang donne exactement le même asm qu'avec __builtin_add_overflow:
Sinon, la solution la plus simple à laquelle je peux penser est la suivante (avec l'interface utilisée par Jens):
gcc et clang génèrent un asm très similaire . gcc donne ceci:
Nous voulons calculer la somme
unsigned
, doncunsigned
nous devons être en mesure de représenter toutes les valeurs deint
sans qu'aucune d'entre elles ne collent ensemble. Pour convertir facilement le résultat deunsigned
enint
, l'inverse est également utile. Dans l'ensemble, le complément à deux est supposé.Sur toutes les plates-formes populaires, je pense que nous pouvons convertir de
unsigned
àint
par une simple affectation commeint sum = u;
mais, comme Jens l'a mentionné, même la dernière variante de la norme C2x lui permet d'augmenter le signal. La prochaine façon la plus naturelle est de faire quelque chose comme ça:*(unsigned *)&sum = u;
mais les variantes de remplissage non-trap pourraient apparemment différer pour les types signés et non signés. L'exemple ci-dessus va donc très mal. Heureusement, gcc et clang optimisent cette conversion délicate.PS Les deux variantes ci-dessus n'ont pas pu être comparées directement car elles ont un comportement différent. La première fait suite à la question d'origine et n'encombre pas
*value
en cas de débordement. Le second suit la réponse de Jens et frappe toujours la variable pointée par le premier paramètre mais elle est sans branche.la source
la meilleure version que je puisse proposer est:
qui produit:
la source
int
, une distribution d'un type plus large produira une valeur définie par l'implémentation ou augmentera un signal. Toutes les implémentations qui m'intéressent le définissent pour préserver le modèle de bits qui fait la bonne chose.Je pourrais amener le compilateur à utiliser l'indicateur de signe en supposant (et en affirmant) une représentation du complément à deux sans remplissage d'octets. Ces mises en œuvre devraient produire le comportement requis dans la ligne annotée par un commentaire, bien que je ne puisse pas trouver de confirmation formelle positive de cette exigence dans la norme (et il n'y en a probablement pas).
Notez que le code suivant ne gère que l'addition d'entiers positifs, mais peut être étendu.
Cela donne à la fois clang et GCC:
la source
_Static_assert
ne sert pas à grand - chose, parce que cela est trivialement vrai sur toute architecture actuelle, et sera même imposée pour C2x.INT_MAX
. Je vais éditer le post. Mais là encore, je ne pense pas que ce code devrait être utilisé dans la pratique de toute façon.