126 votes

Pourquoi le dépassement d'entier sur x86 avec GCC provoque-t-il 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épassement des nombres entiers signés est un comportement techniquement non défini. Mais GCC sur x86 implémente l'arithmétique des nombres entiers en utilisant les instructions x86 pour les nombres entiers - qui s'enroulent sur le débordement.

Par conséquent, je me serais attendu à ce qu'il s'enroule en cas de débordement - malgré le fait que ce soit un comportement non défini. Mais ce n'est clairement pas le cas. Alors qu'est-ce que j'ai manqué ?

Je l'ai compilé 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 ce programme et donne le résultat suivant :

Sortie correcte :

~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3

Voici d'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 pertinentes sur la version :

~/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 la suivante : Est-ce un bug de GCC ? Ou ai-je mal compris quelque chose sur la façon dont GCC gère l'arithmétique des nombres entiers ?

*Je marque également ce C, car je suppose que ce bogue se reproduira en C. (Je ne l'ai pas encore vérifié.)

EDITAR:

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

174voto

bdonlan Points 90068

Quand la norme dit que c'est un comportement non défini, il signifie qu'il . Tout peut arriver. "Tout" inclut "en général, les entiers s'enroulent autour, mais à l'occasion, des choses bizarres se produisent".

Oui, sur les processeurs x86, les entiers généralement s'emballent comme vous le souhaitez. C'est l'une de ces exceptions. Le compilateur suppose que vous ne provoquerez pas de comportement indéfini, et optimise le test de la boucle. Si vous voulez vraiment du wraparound, passez -fwrapv a g++ o 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.

18voto

Dennis Points 6145

C'est simple : Un comportement indéfini - surtout avec l'optimisation ( -O2 ) activée - signifie tout ce qui est peut se produire.

Votre code se comporte comme (vous) l'aviez prévu sans l'option -O2 interrupteur.

Cela fonctionne très bien avec icl et tcc d'ailleurs, mais on ne peut pas se fier à ce genre de choses...

Selon este L'optimisation de gcc exploite en fait un dépassement d'entier signé. Cela signifierait que le "bogue" est de conception.

12voto

Mankarse Points 22800

Il est important de noter ici que les programmes C++ sont écrits pour la machine abstraite C++ (qui est généralement émulée par des instructions matérielles). Le fait que vous compilez pour x86 est totalement sans rapport avec le fait que cela a un comportement indéfini.

Le compilateur est libre d'utiliser l'existence d'un comportement non défini pour améliorer ses optimisations (en supprimant une conditionnelle d'une boucle, comme dans cet exemple). Il n'y a pas de correspondance garantie, ni même utile, entre les constructions de niveau C++ et les constructions de code machine de niveau x86, si ce n'est l'exigence que le code machine, une fois exécuté, produise le résultat demandé par la machine abstraite C++.

4voto

lostyzd Points 1462
i += i;

// le dépassement est indéfini.

Avec -fwrapv, c'est correct. -fwrapv

3voto

vonbrand Points 4785

S'il vous plaît, les gens, comportement indéfini est exactement ça, indéfini . Cela signifie que tout peut arriver. Dans la pratique (comme dans ce cas), le compilateur est libre de supposer qu'elle ne le fera pas être appelé, et faire ce qu'il veut si cela peut rendre le code plus rapide/plus petit. Ce qui se passe avec le code qui ne devrait pas s'exécuter est une question de personne. Cela dépendra du code environnant (en fonction de cela, le compilateur pourrait bien générer un 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 vision différente de la génération de code. Ou alors, il suffit de changer de machine, même un autre modèle de la même ligne d'architecture pourrait très bien avoir son propre comportement non défini (regardez les opcodes non définis, certains programmeurs entreprenants ont découvert que sur certaines de ces premières machines, ils faisaient parfois des choses utiles...). Il existe pas de "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 un comportement cohérent du compilateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X