web-dev-qa-db-fra.com

Bogue de compilation C ++?

J'ai le code suivant:

#include <iostream>
#include <complex>
using namespace std;

int main() {
    complex<int> delta;
    complex<int> mc[4] = {0};

    for(int di = 0; di < 4; di++, delta = mc[di]) {
        cout << di << endl;
    }

    return 0;
}

Je m'attends à ce qu'il affiche "0, 1, 2, 3" et s'arrête, mais il génère une série sans fin de "0, 1, 2, 3, 4, 5, ....."

Il ressemble à la comparaison di<4 ne fonctionne pas bien et renvoie toujours vrai.

Si je commente simplement ,delta=mc[di], J'obtiens "0, 1, 2, 3" comme d'habitude. Quel est le problème avec la mission innocente?

J'utilise Ideone.com g ++ C++ 14 avec l'option -O2.

74
eivour

Cela est dû à un comportement non défini, vous accédez au tableau mc hors limites lors de la dernière itération de votre boucle. Certains compilateurs peuvent effectuer une optimisation de boucle agressive autour des hypothèses d'absence de comportement indéfini. La logique serait similaire à la suivante:

  • Accéder à mc hors limites est un comportement non défini
  • Ne supposez aucun comportement indéfini
  • Par conséquent di < 4 est toujours vrai car sinon mc[di] invoquerait un comportement indéfini

gcc avec l'optimisation activée et en utilisant le -fno-aggressive-loop-optimizations flag fait disparaître le comportement de boucle infinie (voir en direct). Alors qu'un exemple en direct avec optimisation mais sans -fno-agressif-optimisations de boucle présente le comportement de boucle infinie que vous observez.

Un exemple en direct du code Godbolt montre le di < 4 la vérification est supprimée et remplacée par et jmp inconditionnel:

jmp .L6

Ceci est presque identique au cas décrit dans GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks . Les commentaires sur cet article sont excellents et valent bien la lecture. Il note que clang a attrapé le cas dans l'article en utilisant -fsanitize=undefined que je ne peux pas reproduire pour ce cas mais gcc en utilisant -fsanitize=undefined fait (voir en direct). Le bogue le plus tristement célèbre autour d'un optimiseur faisant une inférence autour d'un comportement indéfini est probablement le suppression de la vérification du pointeur nul du noyau Linux .

Bien qu'il s'agisse d'optimisations agressives, il est important de noter que, comme le dit la norme C++, le comportement non défini est:

comportement pour lequel la présente Norme internationale n'impose aucune exigence

Ce qui signifie essentiellement que tout est possible et il note ( c'est moi qui souligne):

[...] Le comportement non défini autorisé va de l'ignorance complète de la situation avec des résultats imprévisibles , au comportement pendant la traduction ou l'exécution du programme d'une manière documentée caractéristique du environnement (avec ou sans émission d'un message de diagnostic), à la fin d'une traduction ou d'une exécution (avec émission d'un message de diagnostic). [...]

Afin d'obtenir un avertissement de gcc, nous devons déplacer le cout en dehors de la boucle, puis nous voyons l'avertissement suivant (voir en direct) :

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
     for(di=0; di<4;di++,delta=mc[di]){ }
     ^

ce qui aurait probablement suffi pour fournir au PO suffisamment d'informations pour comprendre ce qui se passait. De telles incohérences sont typiques des types de comportement que nous pouvons voir avec un comportement non défini. Pour mieux comprendre pourquoi un tel avertissement peut être incohérent face à un comportement indéfini Pourquoi ne pouvez-vous pas avertir lors d'une optimisation basée sur un comportement indéfini? est une bonne lecture.

Remarque, -fno-aggressive-loop-optimizations est documenté dans les notes de version de gcc 4.8 .

109
Shafik Yaghmour

Puisque vous incrémentez di avant de l'utiliser pour indexer mc, la quatrième fois dans la boucle, vous référencerez mc [4], qui est après la fin de votre tableau, ce qui pourrait tourner à conduire à un comportement gênant.

38
Logicrat

C'est parce que di ++ est exécuté lors de la dernière exécution de la boucle.

Par exemple;

int di = 0;
for(; di < 4; di++);
// after the loop di == 4
// (inside the loop we see 0,1,2,3)
// (inside the for statement, after di++, we see 1,2,3,4)

Vous accédez à mc [] lorsque di == 4, c'est donc un problème hors limites, détruisant potentiellement une partie de la pile et corrompant la variable di.

une solution serait:

for(int di = 0; di < 4; di++) {
    cout << di << endl;
    delta = mc[di];
}
5
PaulHK

Tu as ceci:

for(int di=0; di<4; di++, delta=mc[di]) {
  cout<<di<<endl;
}

Essayez plutôt ceci:

for(int di=0; di<4; delta=mc[di++]) {
   cout<<di<<endl;
}

ÉDITER:

Pour clarifier ce qui se passe, décomposons l'itération de votre boucle For:

1ère itération: Initialement di est mis à 0. Vérification de la comparaison: di est-il inférieur à 4? Oui, allez-y. Incrémentez di de 1. Maintenant di = 1. Saisissez l'élément "nth" de mc [] et réglez-le sur delta. Cette fois, nous saisissons le 2e élément car cette valeur indexée est 1 et non 0. Enfin, effectuez le ou les blocs de code à l'intérieur de la boucle for.

2ème itération: maintenant di est réglé sur 1. Vérification de la comparaison: di est-il inférieur à 4? Oui et continuez. Incrémentez di de 1. Maintenant di = 2. Saisissez le "nième" élément de mc [] et réglez-le sur delta. Cette fois, nous saisissons le 3e élément puisque cette valeur indexée est 2. Enfin, effectuez le ou les blocs de code à l'intérieur de la boucle for.

3e itération: maintenant di est réglé sur 2. Vérification de la comparaison: di est-il inférieur à 4? Oui et continuez. Incrémentez di de 1. Maintenant di = 3. Saisissez le "nième" élément de mc [] et réglez-le sur delta. Cette fois, nous saisissons le 4ème élément car cette valeur indexée est 3. Enfin, effectuez le/les bloc (s) de code à l'intérieur de la boucle for.

4ème itération: maintenant di est réglé sur 3. Vérification de la comparaison: di est-il inférieur à 4? Oui et continuez. Incrémentez di de 1. Maintenant di = 4. (Pouvez-vous voir où cela va?) Saisissez le "nième" élément de mc [] et réglez-le sur delta. Cette fois, nous saisissons le 5ème élément puisque cette valeur indexée est 4. Uh Oh, nous avons un problème; notre taille de tableau n'est que de 4. Delta a maintenant des ordures et c'est un comportement indéfini ou une corruption. Enfin, effectuez le ou les blocs de code à l'intérieur de la boucle for en utilisant "delta de déchets".

5ème itération. Maintenant di est réglé sur 4. Vérification de la comparaison: di est-il inférieur à 4? Non, rompez la boucle.

Corruption en dépassant les limites de la mémoire contiguë (tableau).

4
Francis Cugler