C'est UB; en termes ISO C ++, le comportement entier de l'ensemble du programme est complètement non spécifié pour une exécution qui finit par atteindre UB. L'exemple classique est en ce qui concerne la norme C ++, il peut faire sortir les démons de votre nez. (Je déconseille d'utiliser une implémentation où les démons nasaux sont une réelle possibilité). Voir les autres réponses pour plus de détails.
Les compilateurs peuvent "causer des problèmes" au moment de la compilation pour les chemins d'exécution qu'ils peuvent voir, conduisant à une UB visible au moment de la compilation, par exemple en supposant que ces blocs de base ne sont jamais atteints.
Voir aussi Ce que tout programmeur C doit savoir sur le comportement non défini (blog LLVM). Comme expliqué ici, UB avec débordement signé permet aux compilateurs de prouver que les for(... i <= n ...)
boucles ne sont pas des boucles infinies, même pour des inconnues n
. Il leur permet également de "promouvoir" les compteurs de boucles int en largeur de pointeur au lieu de refaire l'extension de signe. (La conséquence de l'UB dans ce cas pourrait donc être d'accéder en dehors des éléments 64k ou 4G bas d'un tableau, si vous vous attendiez à un emballage signé de i
dans sa plage de valeurs.)
Dans certains cas, les compilateurs émettront une instruction illégale comme x86 ud2
pour un bloc qui provoque vraisemblablement UB s'il est exécuté. (Notez qu'une fonction peut ne jamais être appelée, donc les compilateurs ne peuvent généralement pas se déchaîner et casser d'autres fonctions, ou même des chemins possibles à travers une fonction qui n'atteint pas UB. toutes les entrées qui ne mènent pas à UB.)
La solution la plus efficace est probablement de décoller manuellement la dernière itération afin d' factor*=10
éviter les inutiles .
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;
Ou si le corps de la boucle est volumineux, envisagez simplement d'utiliser un type non signé pour factor
. Ensuite, vous pouvez laisser le débordement multiplier non signé et il fera juste un habillage bien défini à une puissance de 2 (le nombre de bits de valeur dans le type non signé).
C'est très bien même si vous l'utilisez avec des types signés, surtout si votre conversion non signée> signée ne déborde jamais.
La conversion entre le signe non signé et le complément à 2 signé est gratuite (même motif binaire pour toutes les valeurs); l'encapsulage modulo pour int -> unsigned spécifié par la norme C ++ simplifie simplement l'utilisation du même motif binaire, contrairement à son complément ou à son signe / amplitude.
Et unsigned-> signed est tout aussi trivial, bien qu'il soit défini par l'implémentation pour des valeurs supérieures à INT_MAX
. Si vous n'utilisez pas l'énorme résultat non signé de la dernière itération, vous n'avez rien à craindre. Mais si vous l'êtes, voir La conversion de non signé en signé n'est-elle pas définie? . Le cas value-does-fit est défini par l'implémentation , ce qui signifie qu'une implémentation doit choisir un comportement; les plus sensés tronquent (si nécessaire) le modèle de bits non signé et l'utilisent comme signé, car cela fonctionne pour les valeurs dans la plage de la même manière sans travail supplémentaire. Et ce n'est certainement pas UB. Les grandes valeurs non signées peuvent donc devenir des entiers signés négatifs. par exemple, après int x = u;
gcc et clang ne pas optimiser loinx>=0
comme toujours vrai, même sans -fwrapv
, car ils définissaient le comportement.