À première vue, cette question peut sembler être une duplication de Comment détecter un dépassement d'entier ? Cependant, elle est en réalité très différente.
J'ai constaté que si la détection d'un dépassement de capacité d'un entier non signé est assez triviale, la détection d'un dépassement de capacité d'un entier non signé est plus difficile. signé Le débordement en C/C++ est en fait plus difficile que la plupart des gens ne le pensent.
La façon la plus évidente, mais aussi la plus naïve, de le faire serait quelque chose comme :
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
Le problème est que, selon la norme C, le dépassement des nombres entiers signés est comportement non défini. En d'autres termes, selon la norme, dès que vous provoquez ne serait-ce qu'un débordement signé, votre programme est tout aussi invalide que si vous déréférenciez un pointeur nul. Vous ne pouvez donc pas provoquer un comportement indéfini, puis essayer de détecter le débordement après coup, comme dans l'exemple de vérification post-condition ci-dessus.
Même si la vérification ci-dessus est susceptible de fonctionner sur de nombreux compilateurs, vous ne pouvez pas compter dessus. En effet, comme la norme C stipule que le débordement d'un entier signé n'est pas défini, certains compilateurs (comme GCC) vont optimiser le contrôle ci-dessus lorsque les drapeaux d'optimisation sont activés, car le compilateur suppose qu'un dépassement de capacité signé est impossible. Cela casse totalement la tentative de vérifier le débordement.
Donc, une autre façon possible de vérifier le débordement serait :
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
Cela semble plus prometteur, puisque nous n'ajoutons pas réellement les deux entiers ensemble avant de nous assurer à l'avance qu'une telle addition n'entraînera pas de dépassement de capacité. Ainsi, nous ne provoquons pas de comportement non défini.
Cependant, cette solution est malheureusement beaucoup moins efficace que la solution initiale, puisque vous devez effectuer une opération de soustraction juste pour tester si votre opération d'addition fonctionnera. Et même si vous ne vous souciez pas de cette (petite) perte de performance, je ne suis toujours pas convaincu que cette solution soit adéquate. L'expression lhs <= INT_MIN - rhs
ressemble exactement au type d'expression que le compilateur pourrait optimiser, en pensant que le dépassement de capacité signé est impossible.
Y a-t-il une meilleure solution ? Quelque chose qui garantisse 1) de ne pas causer de comportement non défini, et 2) de ne pas donner au compilateur l'opportunité d'optimiser les vérifications de débordement ? Je pensais qu'il pourrait y avoir un moyen de le faire en convertissant les deux opérandes en non signés, et en effectuant des vérifications en utilisant votre propre arithmétique de complément à deux, mais je ne suis pas vraiment sûr de savoir comment faire.
1 votes
Plutôt que d'essayer de le détecter, n'est-il pas préférable d'écrire du code qui n'a pas de possibilité de débordement ?
0 votes
Le fait d'avoir la propriété "Comportement non défini" n'implique pas l'équivalence. En d'autres termes, le déréférencement d'un pointeur NULL n'est pas équivalent à un dépassement de capacité signé. UB signifie que la spécification ne précise pas le résultat requis pour être conforme à la spécification. On ne peut donc pas supposer que le système sera dans un état invalide et instable à cause du débordement signé, mais simplement que le résultat numérique ne sera pas prévisible.
10 votes
@ArunSaha : Il est vraiment difficile de prendre des calculs et de garantir qu'ils ne déborderont pas, et il est impossible de le prouver dans le cas général. La pratique habituelle consiste à utiliser un type d'entier aussi large que possible et à espérer.
6 votes
@Amardeep : Le déréférencement d'un pointeur nul est tout aussi indéfini que le débordement signé. Un comportement indéfini signifie que, dans la mesure où la norme va, tout peut arriver. On ne peut pas supposer que le système ne sera pas dans un état invalide et instable après un débordement signé. L'OP a souligné une conséquence de ceci : il est parfaitement légal pour l'optimiseur de supprimer le code qui détecte le signed overflow une fois qu'il se produit.
0 votes
@David : Oui, c'est également indéfini. Mais ce n'est pas mon point de désaccord. Le déréférencement d'un pointeur NULL peut résulter en quelque chose entre rien et un crash du processus. Le débordement signé entraînera une valeur imprévisible dans une variable. Si vous connaissez une mise en œuvre qui devient vraiment invalide ou instable, je serais vraiment intéressé de le savoir.
17 votes
@Amardeep : J'ai mentionné une telle implémentation. GCC va supprimer le code de contrôle de débordement lorsque les drapeaux d'optimisation sont activés. Cela va donc casser votre programme. C'est sans doute pire que le déréférencement d'un pointeur nul, car il peut entraîner de subtiles failles de sécurité, alors que le déréférencement d'un pointeur nul risque d'entraîner une erreur de segmentation dans votre programme.
0 votes
@Amardeep : pas nécessairement. Un résultat possible de l'UB est qu'il recompile votre programme dans un environnement où
int
est un peu plus grand et le redémarre depuis le début ;-)0 votes
@R : Joli... architecture itérative. :-)
0 votes
Descendu pour l'abomination qu'est le C/C++.
0 votes
@Amardeep : Il serait légitime pour une implémentation de déclencher une interruption matérielle lorsqu'un débordement signé se produit, et la norme n'impose aucune exigence en ce qui concerne ce qu'une telle interruption pourrait ou ne pourrait pas faire.
0 votes
@supercat : Je préfère l'idée hypothétique de R. Mais si vous connaissez une telle implémentation, j'aimerais l'étudier.
2 votes
@Amardeep : J'ai certainement vu des implémentations où, selon les paramètres du compilateur, le débordement causerait un piège. Ce serait bien si les langages permettaient de spécifier si des variables ou des quantités non signées particulières doivent (1) s'emballer proprement, (2) faire défaut, ou (3) faire ce qui est pratique. Notez que si une variable est plus petite que la taille du registre d'une machine, exiger que les quantités non signées s'enroulent proprement peut empêcher la génération d'un code optimal.
1 votes
@Amardeep : gcc est une de ces implémentations, si vous compilez votre code avec
-ftrapv
.0 votes
Gilles : J'ai lu des plaintes selon lesquelles le -ftrapv de gcc n'était pas fiable dans au moins certaines versions ; je n'ai aucune idée s'il existe des versions où il fonctionne réellement de manière utile.
0 votes
if (INT_MAX - lhs <= rhs) {
Cela ne devrait-il pas êtreif (INT_MAX - lhs < rhs) {
? Pourlhs = INT_MAX - 1
yrhs = 1
nous ajoutons et c'estINT_MAX
pas de débordement, maisINT_MAX - lhs = 1 == rhs
.0 votes
Ce n'est pas une question et ce n'est pas assez précis, car il y a des entiers non signés et signés et au moins 4 opérations arithmétiques de base (addition, soustraction, multiplication, division).
0 votes
Votre question concerne les routines de dépassement de capacité du compilateur_rt, qui sont au nombre de 5 au total pour la valeur absolue, la négation, l'addition, la soustraction et la multiplication.