2010-09-12 3 views
5

Donc je fais un jeu de serpents avec les téléports et les souris habituelles. J'ai eu une boucle en cours d'exécution comme ceci:Mystérieux heisenbug?

while(snake.alive() && miceEaten < micePerLevel) 
{ 
    displayInfo(lives, score, level, micePerLevel - miceEaten); 
    //some code 
    if(miceEaten()) 
    { 
     //update score... 
    } 
    //more stuff... 
} 

Le problème avec le code ci-dessus est que displayInfo est appelé avant le score est mis à jour, et ainsi après avoir mangé une souris, l'utilisateur doit attendre jusqu'à ce que la boucle fonctionne à nouveau voir son score mis à jour. J'ai donc déplacé cette ligne de code vers le bas de la fonction:

while(snake.alive() && miceEaten < micePerLevel) 
{ 
    //some code 
    if(miceEaten()) 
    { 
     //update score... 
    } 
    //more stuff... 
    displayInfo(lives, score, level, micePerLevel - miceEaten); 
} 

et les téléportations s'arrêtent de fonctionner! Le programme se bloque chaque fois que le serpent atteint un téléport. Et displayInfo utilise le code suivant:

stringstream s; 
s << "LEVEL " << left << setw(12) << level << "LIVES: " << setw(12) << lives << "MICE LEFT: " << setw(12) << miceLeft 
    << "SCORE: " << setw(13) << score; 
printLine(0, s.str(), WHITEONBLUE); 

printLine a seulement color_set, mvprintw et refresh(). Rien à voir avec Teleports. Bizarre.

donc je suis allé à la fonction de serpent où le serpent tire son prochain emplacement d'une téléportation:

body.push_back(teleports[overlap(next)]->teleportFrom(dir)); //next is a Location object 

teleports[overlap(next)]->teleportFrom(dir) retourne l'emplacement du serpent doit être téléporté. Pour tenter de comprendre pourquoi il plantait (peut-être Teleport rentrait un emplacement hors-champ?), J'ai ajouté les 3 lignes suivantes avant la ligne ci-dessus:

Location l = teleports[overlap(next)]->teleportFrom(dir); 
    mvprintw(1, 0, "(%i, %i)", l.x, l.y); 
    refresh(); 

Et le problème disparait!

Non seulement cela, mais je dois avoir ces trois lignes. Si je commente mvprintw(1, 0, "(%i, %i)", l.x, l.y);, ou refresh();, ou les deux, le programme se bloque comme avant en atteignant une téléportation.

Des idées sur ce qui pourrait causer ce comportement?

MISE À JOUR: J'ai essayé de supprimer tous les avertissements (qui étaient pour la plupart des avertissements au sujet des comparaisons de signature/numéros non signés), mais seulement 1 reste à ce jour:

warning: reference to local variable 'other' returned 

Et le code:

Location& Location::operator = (Location other) 
{ 
    if(this == &other) 
     return other; 
    x = other.x; 
    y = other.y; 
    return *this; 
} 

Que faire pour corriger cet avertissement?

+2

Vous obtenez un comportement indéfini quelque part. (Vous êtes probablement en train d'accéder à un objet hors limites et de vider la pile, les données environnantes ou quoi que ce soit d'autre.) Vous devez parcourir votre code et vérifier tous vos accès. – GManNickG

+2

Vous avez un comportement indéfini ailleurs dans votre code. Compilez votre code avec tous les avertissements activés. Et assurez-vous qu'aucun avertissement n'est signalé (les avertissements sont généralement des erreurs). La façon dont vous activez les avertissements dépend du compilateur. Si cela ne fonctionne pas, vous devez utiliser un outil qui vous informera de la corruption de la mémoire (encore dépendante de la plate-forme). –

+0

@Martin York - J'ai essayé de supprimer tous les avertissements, s'il vous plaît voir ma mise à jour. Je pense que cet avertissement peut être la cause, mais j'ai besoin d'aide. Merci! – wrongusername

Répondre

7

Construire votre opérateur d'affectation comme ceci:
Vous devriez toujours retourner * ceci (même s'ils étaient égaux). Mais ils ne le feraient jamais puisque vous étiez en train de créer une copie locale (donc ce n'était pas votre erreur).

Location& Location::operator = (Location const& other) 
{ 
    // Does it really matter if you assign to self? 
    x = other.x; 
    y = other.y; 
    return *this; 
} 

La copie standard et d'échange semblait un peu exagéré pour une telle classe simple.

PS. Vous devriez corriger tous les avertissements (même s'ils sont aussi simples que la non-concordance non signée). Si vous ne les réparez pas, vous deviendrez immunisé contre leur puissance et ne détecterez pas un vrai problème car il est entouré d'un avertissement que vous ignorez. Donc, corrigez-les tous (j'ai toujours activé le drapeau qui fait que le compilateur traite tous les avertissements comme des erreurs de sorte que le code ne compile pas s'il y a des avertissements).

La bonne façon de mettre en œuvre l'opérateur d'affectation (ou la bonne manière la plus communément acceptée). Est d'utiliser le langage de copie et d'échange:

// notice the parameter is passed by value (i.e. a copy). 
// So the copy part is aromatically taken care of here. 
// So now you just need tom implement the swap() part of the idiom. 
Location& Location::operator = (Location other) 
{ 
    this->swap(other); 
    return *this; 
} 

void Location::swap(Location& other) 
{ 
    std::swap(x, other.x); 
    std::swap(y, other.y); 
} 
+0

Hehe, mon professeur a écrit ça en classe :) Mais avec ce que vous avez écrit, il y a 0 avertissements, et le code fonctionne bien. Merci! – wrongusername

+1

Vous ne pouvez pas toujours traiter tous les avertissements comme des erreurs. Trop souvent, il y a des en-têtes de système dont vous avez besoin (en particulier les plus obscurs) qui produisent toujours des avertissements lorsqu'ils sont utilisés, même s'ils fonctionnent bien. La meilleure façon de progresser dans de telles situations est de mettre en quarantaine l'utilisation de l'en-tête incriminé dans un fichier source unique afin qu'au moins le reste du code puisse être sans avertissement. –

+0

@wrongusername @Martin: La seule fois où vous utilisez l'idiome copy-and-swap est quand vous avez besoin d'écrire les Big Three. Est-ce que 'Location' a réellement besoin d'un opérateur d'assignation personnalisé? (Puisque vous ne gérez rien, j'en doute!) – GManNickG

3

Avez-vous vérifié votre code qui cause cette anomalie? Les Heisenbug phénomènes cités ici:

Un exemple commun est un bug qui se produit dans un programme qui a été compilé avec un compilateur optimisé, mais pas dans le même programme lors de la compilation sans optimisation (par exemple, pour générer un mode debug Version)

Voici quelques lignes directrices:

  • condition de course? utilisez-vous des threads?
  • Limite de dépassement de pointeur quelque part?
  • exécuter votre code par valgrind pour surveiller tout changement inhabituel/irrégulier dans les tampons de mémoire quelque part

Une autre citation:

Une raison commune pour le comportement comme Heisenbug est que l'exécution d'un programme de débogage Le mode nettoie souvent la mémoire avant le démarrage du programme et force les variables sur les emplacements de la pile, au lieu de les conserver dans les registres. Ces différences dans l'exécution peuvent modifier l'effet des bogues impliquant un accès de membre hors limites ou des suppositions incorrectes sur le contenu initial de la mémoire. Une autre raison est que les débogueurs fournissent généralement des montres ou d'autres interfaces utilisateur qui provoquent l'exécution de code supplémentaire (comme des accesseurs de propriété), ce qui peut, à son tour, modifier l'état du programme. Encore une autre raison est un fandango sur le noyau, l'effet d'un pointeur en dehors des limites. En C++, beaucoup de heisenbugs sont causés par des variables non initialisées.

Assurez-vous que les interrupteurs sont éteints - aucune optimisation, complète les informations de débogage, de supprimer tous les builds existant, redémarrez l'IDE et recompiler ....

+2

+1 pour valgrind. (Battez-moi à lui aussi.) –

+0

Pas de discussions - Je n'ai pas encore appris à les utiliser. Va vérifier valgrind, mais comment saurai-je s'il y a des changements inhabituels? Et qu'est-ce qu'un débordement de pointeur? J'ai activé tous les avertissements sur le compilateur et ne vois aucune case d'optimisation cochée. Merci! – wrongusername

+0

@wrongusername: quel compilateur utilisez-vous? – t0mm13b

4
Location& Location::operator = (Location other) 
{ 
    if(this == &other) 
     return other; 
    x = other.x; 
    y = other.y; 
    return *this; 
} 

Cela renvoie une référence. Lorsque la fonction revient, qu'arrive-t-il à other? (Il meurt, et vous ne faites référence à rien.) Puisque c'est la classe dont vous avez à traiter autour de la zone à problème, c'est probablement la cause. Réorganiser le code environnant quitte la pile dans une certaine condition où la référence à la variable morte "fonctionne".

Changez-le en return *this, ou supprimez simplement la vérification. (Affectation deux variables sans une branche se déroulera probablement toujours plus vite que l'ajout d'une branche, sur un processeur moderne.)

(Vous devez prendre aussi généralement le paramètre par référence, au lieu de par valeur.)

+1

Le test 'this == & other' sera TOUJOURS faux, car' other' sera toujours une temp allouée sur la pile. Donc, ce code, bien que médiocre, n'est probablement pas le problème. Pour réparer l'avertissement, sortez le test et retour rapide. –

+0

@Chris: Ah, bon point. – GManNickG

+0

Le problème était que l'opérateur d'affectation de l'OP retournait une référence à la variable locale (paramètre call by value). Est-ce exact? – Chubsdad

1

Tout d'abord, votre emplacement :: operator = devrait être comme ceci:

Location& Location::operator = (const Location &other) 
{ 
    if(this == &other) 
     return *this; 
    x = other.x; 
    y = other.y; 
    return *this; 
} 

Cependant, que probablement ne précise pas l'accident. Les mauvais pointeurs sur la pile ne tombent pas sur la plupart des architectures (en supposant que x et y sont des entiers).

Maintenant, c'est un mandelbug, pas un heisenbug.Vous avez quelqu'un d'autre quelque part corrompant la mémoire. Bonne chance.

Questions connexes