web-dev-qa-db-fra.com

Pourquoi le débordement d'entier sur x86 avec GCC provoque une boucle infinie?

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;
}

Alors voici l'affaire: Le débordement d'entier signé est un comportement techniquement indéfini. Mais GCC sur x86 implémente l'arithmétique d'entier en utilisant des instructions d'entier x86 - qui encapsulent le débordement.

Par conséquent, je m'attendais à ce qu'il se termine par un débordement - malgré le fait qu'il s'agit d'un comportement non défini. Mais ce n'est clairement pas le cas. Donc ... Qu'est ce que j'ai manqué?

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 le compile également correctement 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$ 

Donc la question est: Est-ce un bug dans GCC? Ou ai-je mal compris quelque chose sur la façon dont GCC gère l'arithmétique entière?

* 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 reconnu correctement)

.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
125
Mysticial

Lorsque la norme dit que c'est un comportement indéfini, cela signifie que . Tout peut arriver. "Tout" inclut "généralement des nombres entiers, mais parfois des choses étranges se produisent".

Oui, sur les processeurs x86, les entiers généralement se déroulent comme vous l'attendez. C'est 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 envelopper, passez -fwrapv à g++ ou gcc lors de la compilation; cela vous donne une sémantique de débordement bien définie (complément à deux), mais peut nuire aux performances.

171
bdonlan

C'est simple: comportement indéfini - en particulier avec l'optimisation (-O2) activé - signifie que tout peut se produire.

Votre code se comporte comme (vous) vous attendiez sans le -O2 commutateur.

Soit dit en passant, cela fonctionne très bien avec icl et tcc, mais vous ne pouvez pas compter sur des trucs comme ça ...

Selon this , l'optimisation gcc exploite en fait le débordement d'entier signé. Cela signifierait que le "bug" est de par sa conception.

18
Dennis

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 compilez pour x86 est totalement sans rapport avec le fait que cela a un comportement non défini.

Le compilateur est libre d'utiliser l'existence d'un comportement non dé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, mis à part l'exigence selon laquelle le code machine, une fois exécuté, produira le résultat demandé par la machine abstraite C++.

11
Mankarse
i += i;

// le débordement n'est pas défini.

Avec -fwrapv, c'est correct. - fwrapv

4
lostyzd

S'il vous plaît les gens, comportement indéfini est exactement cela, indéfini. Cela signifie que tout pourrait arriver. En pratique (comme dans ce cas), le compilateur est libre de supposer qu'il ne sera pas être appelé, et de faire tout ce qui lui plaît si cela pourrait 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 quiconque. 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 tout 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 indéfinis, certains programmeurs entreprenants ont découvert que sur certaines de ces premières machines faisaient parfois des choses utiles ...) . Il y a non "le compilateur donne un comportement défini sur un comportement indéfini". Il y a des zones qui sont définies par l'implémentation, et là vous devriez pouvoir compter sur le compilateur se comportant de manière cohérente.

3
vonbrand

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 à l'annexe L), le résultat d'un débordement d'entier devrait, en l'absence d'une promesse de plate-forme spécifique d'un comportement plus spécifique, être considérée au minimum comme une "valeur partiellement indéterminée". En vertu de ces règles, l'ajout de 1073741824 + 1073741824 pourrait arbitrairement être considéré comme produisant 2147483648 ou -2147483648 ou toute autre valeur conforme au 2147483648 mod 4294967296, et les valeurs obtenues par ajouts pourraient arbitrairement être considérées comme toute valeur conforme au 0 mod 4294967296.

Des règles permettant au débordement 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 limités. Comportement indéfini. Cela empêcherait un compilateur de faire des "optimisations" bidon dont l'effet principal dans de nombreux cas est d'exiger que les programmeurs ajoutent de l'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.

1
supercat