Le code suivant entre dans une boucle infinie sur GCC:
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
Voici donc l'affaire: le dépassement d'entier signé est un comportement techniquement indéfini. Mais GCC sur x86 implémente l'arithmétique d'entiers en utilisant des instructions d'entiers x86 - qui s'enroulent en cas de débordement.
Par conséquent, je me serais attendu à ce qu'il se termine en cas de débordement - malgré le fait qu'il s'agisse d'un comportement non défini. Mais ce n'est clairement pas le cas. Et alors ... qu'est-ce que j'ai loupé?
J'ai compilé ceci en utilisant:
~/Desktop$ g++ main.cpp -O2
Sortie GCC:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
Avec les optimisations désactivées, il n'y a pas de boucle infinie et la sortie est correcte. Visual Studio compile également correctement ceci et donne le résultat suivant:
Sortie correcte:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
Voici quelques autres variantes:
i *= 2; // Also fails and goes into infinite loop.
i <<= 1; // This seems okay. It does not enter infinite loop.
Voici toutes les informations de version pertinentes:
~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..
...
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)
~/Desktop$
La question est donc: s'agit -il d'un bogue dans GCC? Ou ai-je mal compris quelque chose sur la façon dont GCC gère l'arithmétique des entiers?
* Je marque également ce C, car je suppose que ce bogue se reproduira en C. (je ne l'ai pas encore vérifié.)
ÉDITER:
Voici l'assemblage de la boucle: (si je l'ai bien reconnue)
.L5:
addl %ebp, %ebp
movl $_ZSt4cout, %edi
movl %ebp, %esi
.cfi_offset 3, -40
call _ZNSolsEi
movq %rax, %rbx
movq (%rax), %rax
movq -24(%rax), %rax
movq 240(%rbx,%rax), %r13
testq %r13, %r13
je .L10
cmpb $0, 56(%r13)
je .L3
movzbl 67(%r13), %eax
.L4:
movsbl %al, %esi
movq %rbx, %rdi
addl $1, %r12d
call _ZNSo3putEc
movq %rax, %rdi
call _ZNSo5flushEv
cmpl $3, %r12d
jne .L5
gcc -S
.Réponses:
Lorsque la norme dit que c'est un comportement non défini, cela le signifie . Tout peut arriver. "Tout" inclut "généralement des entiers, mais parfois des choses étranges se produisent".
Oui, sur les processeurs x86, les entiers enveloppent généralement la façon dont vous vous attendez. C'est l'une de ces exceptions. Le compilateur suppose que vous ne provoquerez pas de comportement indéfini et optimise le test de boucle. Si vous voulez vraiment un wraparound, passez
-fwrapv
àg++
ougcc
lors de la compilation; cela vous donne une sémantique de débordement bien définie (complément à deux), mais peut nuire aux performances.la source
-fwrapv
. Merci de l'avoir signalé.C'est simple: un comportement non défini - en particulier lorsque l'optimisation (
-O2
) est activée - signifie que tout peut arriver.Votre code se comporte comme (vous) attendiez sans le
-O2
commutateur.Cela fonctionne assez bien avec icl et tcc, mais vous ne pouvez pas vous fier à des trucs comme ça ...
Selon cela , l'optimisation gcc exploite en fait le débordement d'entiers signés. Cela signifierait que le "bogue" est par conception.
la source
for (j = i; j < i + 10; ++j) ++k;
, il sera simplement définik = 10
, car cela sera toujours vrai si aucun débordement signé ne se produit.La chose importante à noter ici est que les programmes C ++ sont écrits pour la machine abstraite C ++ (qui est généralement émulée via des instructions matérielles). Le fait que vous compiliez pour x86 n'a aucun rapport avec le fait que cela a un comportement non défini.
Le compilateur est libre d'utiliser l'existence d'un comportement indéfini pour améliorer ses optimisations (en supprimant un conditionnel d'une boucle, comme dans cet exemple). Il n'y a pas de mappage garanti, ni même utile, entre les constructions de niveau C ++ et les constructions de code machine de niveau x86 en dehors de l'exigence que le code machine, une fois exécuté, produise le résultat demandé par la machine abstraite C ++.
la source
// le débordement n'est pas défini.
Avec -fwrapv c'est correct. -fwrapv
la source
S'il vous plaît les gens, un comportement indéfini est exactement cela, indéfini . Cela signifie que tout peut arriver. En pratique (comme dans ce cas), le compilateur est libre de supposer qu'il ne le fera pasêtre appelé, et faites ce qui vous plaît si cela peut rendre le code plus rapide / plus petit. Ce qui se passe avec du code qui ne devrait pas s'exécuter est une supposition de personne. Cela dépendra du code environnant (en fonction de cela, le compilateur pourrait bien générer du code différent), des variables / constantes utilisées, des drapeaux du compilateur, ... Oh, et le compilateur pourrait être mis à jour et écrire le même code différemment, ou vous pourriez obtenir un autre compilateur avec une vue différente sur la génération de code. Ou simplement obtenir une machine différente, même un autre modèle de la même ligne d'architecture pourrait très bien avoir son propre comportement indéfini (recherchez des opcodes non définis, certains programmeurs entreprenants ont découvert que sur certaines de ces premières machines faisaient parfois des choses utiles ...) . Il y a pas"le compilateur donne un comportement défini sur un comportement non défini". Il y a des domaines qui sont définis par l'implémentation, et là, vous devriez pouvoir compter sur le comportement cohérent du compilateur.
la source
Même si un compilateur devait spécifier que le débordement d'entier doit être considéré comme une forme "non critique" de comportement indéfini (tel que défini dans l'annexe L), le résultat d'un débordement d'entier devrait, en l'absence d'une plate-forme spécifique promesse d'un comportement plus spécifique, être au minimum considérée comme une "valeur partiellement indéterminée". Selon de telles règles, l'ajout de 1073741824 + 1073741824 pourrait arbitrairement être considéré comme donnant 2147483648 ou -2147483648 ou toute autre valeur qui était congruente à 2147483648 mod 4294967296, et les valeurs obtenues par des ajouts pourraient arbitrairement être considérées comme toute valeur qui était congruente à 0 mod 4294967296.
Les règles permettant aux débordements de produire des "valeurs partiellement indéterminées" seraient suffisamment bien définies pour respecter la lettre et l'esprit de l'annexe L, mais n'empêcheraient pas un compilateur de faire les mêmes inférences généralement utiles que celles qui seraient justifiées si les débordements n'étaient pas contraints Comportement indéfini. Cela empêcherait un compilateur de faire de fausses "optimisations" dont le principal effet dans de nombreux cas est d'exiger que les programmeurs ajoutent un encombrement supplémentaire au code dont le seul but est d'empêcher de telles "optimisations"; que ce soit une bonne chose ou non dépend de son point de vue.
la source