IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Comportements différents dans main() et dans foo()


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    101
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 101
    Par défaut Comportements différents dans main() et dans foo()
    Bonjour

    Je n'ai jamais eu ce problème. Et sans doute vous non plus !
    Une copie du debugging vaudra mieux qu'un long discours.
    Nom : foo_bug.JPG
Affichages : 112
Taille : 103,7 Ko


    Comme on peut le constater, la première partie de main() est un copier/coller de foo().
    1) la première partie de main() fonctionne bien (voir la console)
    2) foo() dysfonctionne complètement. Le debugging montre que
    * pour x = 0 on a (x != 0) = true (cf b1) ce qui donne une boucle infinie à l'exécution.
    * pour j = 48 on a (j > 15) = false (cf b2) ce qui ne stoppe pas la boucle infinie.

    Je n'y comprends rien ! Toute idée est la bienvenue. Merci d'avance.

    PS : en général __builtin_clzll() n'est pas définie en 0. La variable 'j' est donc aléatoire pour x = 0 : la valeur '48' n'est donc pas reproductible.
    Il est d'ailleurs très probable que ce bug ne le soit pas non plus !

    Contexte : Windows 10 // Code::Blocs 25.03 // MinGW_w64 Compiler (-O3)

  2. #2
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    101
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 101
    Par défaut
    Bonjour

    Désolé d'être intervenu pour rien (un modérateur peut peut-être supprimer ce fil).

    Le coupable est le uint32_t devant foo() alors qu'il n'y a pas de return. Le programme tourne en attendant ce 'return' et a donc un comportement imprévisible. Le seul point positif de ceci est que face à un comportement invraissemblable il faut chercher l'erreur dans cette direction.

  3. #3
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 584
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 584
    Par défaut
    Bonsoir et merci pour le retour d'expérience.

    Il n'y a pas lieu de supprimer ici. Ce serait dommage de le faire car ton problème initial est proprement documenté et parce que la solution peut faire gagner beaucoup temps à ceux qui commettent le même oubli.

  4. #4
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    101
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 101
    Par défaut
    Merci Obsidian

    Que le programme tourne en rond : OK.
    Mais même en remplaçant b1 = (x != 0) par b1 = static_cast<bool>(x) on obtient toujours
    'b1 = 1' pour 'x = 0' . C'est quand même dur à avaler !

    PS : en examinant plus attentivement le 'Build messages' j'ai vu un warning signalant le problème.
    Est-ce qu'un warning est suffisant pour ce genre d'erreur ?

  5. #5
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 584
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 584
    Par défaut
    Bonjour,
    J'ai relu ton précédent message un peu plus attentivement. Il y a quand même quelque chose qui m'ennuie :

    Citation Envoyé par serge17 Voir le message
    Mais même en remplaçant b1 = (x != 0) par b1 = static_cast<bool>(x) on obtient toujours
    'b1 = 1' pour 'x = 0' . C'est quand même dur à avaler !
    Je lis :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    while (x != 0)
    {
        …
        b1 = (x != 0); cout<<"\nb1 = "<<b1;
        …
        …
    }
    b1 est définie dans la la boucle, qui est inféodée à la même condition. Cela signifie qu'elle sera définie et le message affiché qu'à condition que x soit déjà différente de zéro.

    Par ailleurs, tu mets des retours à la ligne en début de message mais pas à la fin. Donc il est possible que chaque message émis reste dans le tampon jusqu'à l'émission du suivant.

    Maintenant, ça ne devrait pas empêcher non plus ta boucle de se terminer car dans le cas d'un entier non signé << et <<= ne sont pas des UB dans le cas d'un débordement. En revanche, on ne sait pas à quoi sert cette fameuse fonction __builtin_clzll() ni même si c'est une fonction pure. Si toutefois x lui est passée par référence et que son nom signifie « clear and zero (long long) » et donc qu'à tout hasard, elle remette systématiquement x à zéro, alors effectivement ta boucle ne se terminerait jamais parce que cette remise à zéro serait toujours suivie d'un x <<= 1 juste avant la fin du bloc.

    UPDATE : __builtin_clzll() est une fonction builtin de GCC : https://gcc.gnu.org/onlinedocs/gcc/B...-Builtins.html

    Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
    En recopiant ta fonction et en l'exécutant sous GDB, j'aboutis à un SIGILL qui est en fait provoqué explicitement par une instruction x86 « ud2 », qui sert à émuler ce comportement, probablement pour respecter l'algo de l'instruction BSR dédiée à cela : https://www.felixcloutier.com/x86/bsr

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 584
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 584
    Par défaut
    Je viens de faire le test un peu plus loin :

    • Avec g++ -O0, j'obtiens le comportement décrit ci-dessus ;
    • Avec g++ -O3, j'entre dans une boucle infinie (UPDATE : avec -O1 à -O3, en réalité).


    Il s'agit donc bien d'un undefined behavour, et à très bas niveau.

    Voici le code désassemblé et commenté par mes soins de la fonction foo(), produit avec l'option -S de g++, puis vérifié avec gdb et objdump --disassemble :

    Code asm : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
     
     
    .L2:
            movl    $12, %edx          ; Longueur chaîne = 12
            movl    $.LC0, %esi        ; "\n x debut = "
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
     
            movq    %rbx, %rsi         ; (RBX = x)
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZNSo9_M_insertImEERSoT_
     
            movl    $7, %edx           ; Longueur chaîne = 7
            movl    $.LC1, %esi        ; "\n b1 = "
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
     
            movl    $1, %esi           ; (int)1
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZNSo9_M_insertIbEERSoT_
     
            bsrq    %rbx, %rbp         ; Instruction BSR, pour __builtin_clzll(). n = rang du premier bit à 1
             --
            movl    $7, %edx           ; Longueur chaîne = 7
            movl    $.LC2, %esi        ; "\n b2 = "
            movl    $_ZSt4cout, %edi   ; cout
             --
            xorq    $63, %rbp          ; XOR 63 sur 7 bits => « -n-1 » => Nombre de bits à zéro à gauche
             --
            call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
     
            xorl    %esi, %esi         ; (int)0
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZNSo9_M_insertIbEERSoT_
     
            movl    %ebp, %ecx
             --
            movl    $8, %edx           ; Longueur chaîne = 8
            movl    $.LC3, %esi        ; "\n x fin "
             --
            salq    %cl, %rbx          ; x <<= j
             --
            movl    $_ZSt4cout, %edi   ; cout
            call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
     
            addq    %rbx, %rbx         ; x = x + x  =>  x *=2  =>  x <<= 1
     
            movl    $_ZSt4cout, %edi   ; cout
            movq    %rbx, %rsi         ; (RBX = x)
            call    _ZNSo9_M_insertImEERSoT_
     
            movl    $8, %edx           ; Longueur chaîne = 8
            movl    $.LC4, %esi        ; "\n ------"
            movq    %rax, %rdi
            call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
     
            jmp     .L2

    On remarque que ce code ne contient aucune comparaison ni test de nullité, et que l'unique saut est le jump en fin de procédure qui remonte au début. On constate également deux choses :

    • Tout se passe donc comme si le compilateur, ici, faisait abstraction du format de x. En conséquence, comme tu n'effectues que des décalages vers la gauche, x ne peut arithmétiquement jamais nul s'il n'est pas explicitement borné. Le compilateur fait donc l'économie du contrôle de la valeur de x en début de boucle ;
    • b1 et b2 sont considérées comme des constantes, ce qui confirme que x est considéré comme n'étant jamais nul et qui implique que le compilateur « sait » que j ne sera jamais supérieur à q. Pour la même raison, la ligne suivante est totalement ignorée et n'apparaît jamais dans le code compilé.


    Pour b2, cela se démontre : si x démarre à 1<<48 donc avec 7 bits nuls à gauche, que x n'est jamais nul (conditionné par la boucle) et qu'on ne fait que le déplacer vers la gauche, alors tant qu'il restera des bits à 1 dedans, il y aura forcément moins de 7 bits nuls restants à chaque tour (et a fortiori moins de 15).

    Pour le reste, il est difficile de dire exactement dans quelle mesure il s'agit ou non d'un bug de GCC. D'un côté, conditionner le contrôle d'une boucle au débordement de la variable peut être considéré a priori comme un UB mais de l'autre, l'application d'un modulo sur le MAX_SIZE+1 du type à condition qu'il soit non signé est défini explicitement aussi bien dans C++11 que dans C11.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. images diffrentes dans l' infobulle
    Par lloyd450 dans le forum APIs Google
    Réponses: 0
    Dernier message: 22/03/2012, 16h05
  2. Comportement différent dans JTable
    Par scorbo dans le forum Composants
    Réponses: 3
    Dernier message: 21/03/2012, 14h05
  3. Comportement étrange dans boucles foreach
    Par Tyra3l dans le forum Langage
    Réponses: 5
    Dernier message: 17/02/2012, 23h30
  4. Comportement bizarre dans POI
    Par bruno.wiesen dans le forum Documents
    Réponses: 1
    Dernier message: 23/05/2007, 11h04

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo