2010-09-12 21 views
5

Quindi sto facendo un gioco di serpenti con i teletrasporti e i soliti topi. Ho avuto un ciclo in esecuzione come questo:Misterioso heisenbug?

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

Il problema con il codice di cui sopra è che displayInfo viene chiamato prima che il punteggio viene aggiornato, e così dopo aver mangiato un topo, l'utente deve attendere che il ciclo viene eseguito di nuovo per guarda il suo punteggio aggiornato. Così ho spostato quella riga di codice nella parte inferiore della funzione:

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

e teleport smettono di funzionare! Il programma si blocca quando il serpente raggiunge un teletrasporto. E displayInfo utilizza il seguente codice:

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

Dove printLine ha solo una color_set, mvprintw e refresh(). Niente a che vedere con Teleports. Strano.

Così sono andato alla funzione serpente in cui il serpente prende il prossimo luogo da un teletrasporto:

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

Dove teleports[overlap(next)]->teleportFrom(dir) restituisce la posizione del serpente deve essere teletrasportato a. Nel tentativo di capire perché si è in crash (forse Teleport stava tornando una certa posizione fuori campo?), Ho aggiunto le seguenti 3 righe prima della riga sopra:

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

E il problema scompare!

Non solo, ma DEVO avere queste tre linee. Se commento fuori mvprintw(1, 0, "(%i, %i)", l.x, l.y);, o refresh();, o entrambi, il programma si blocca come prima al raggiungimento di un teletrasporto.

Qualche idea su cosa potrebbe causare questo comportamento?

UPDATE: Ho provato a rimuovere tutti gli avvisi (che erano per lo più gli avvertimenti circa i confronti di firma/numeri senza segno), ma solo 1 rimane finora:

warning: reference to local variable 'other' returned 

E il codice:

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

Cosa devo fare per correggere questo avviso?

+2

Si sta ottenendo un comportamento indefinito da qualche parte. (Probabilmente, stai accedendo a qualcosa di fuori limite e rovinando la pila, i dati circostanti o qualsiasi altra cosa.) È necessario scorrere il codice e controllare tutti i tuoi accessi. – GManNickG

+2

Hai qualche comportamento non definito da qualche altra parte nel tuo codice. Compila il tuo codice con tutti gli avvisi attivati. E assicurarsi che non ci siano avvisi segnalati (gli avvertimenti sono di solito errori). Come si attivano gli avvisi dipenderà dal compilatore. Se ciò non funziona, è necessario utilizzare uno strumento che ti informi sulla corruzione della memoria (sempre dipendente dalla piattaforma). –

+0

@Martin York - Ho provato a rimuovere tutti gli avvisi, per favore vedi il mio aggiornamento. Penso che questo avvertimento possa essere la causa, ma ho bisogno di aiuto. Grazie! – wrongusername

risposta

7

Corporatura il tuo operatore di assegnazione come questo:
Dovresti sempre restituire * questo (anche se erano uguali). Ma non lo farebbero mai da quando stavi creando una copia locale (quindi questo non era un tuo errore).

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

La copia e lo scambio standard sembravano un po 'eccessivo per una classe così semplice.

PS. È necessario correggere tutti gli avvisi (anche se sono semplici come la mancata corrispondenza senza segno). Se non li risolvi, diventerai immune alla loro potenza e non individuerai un problema reale perché è circondato dall'avvertimento che stai ignorando. Quindi aggiustali tutti (a) Attiva sempre il flag per far sì che il compilatore consideri tutti gli avvertimenti come errori, in modo che il codice non venga compilato in caso di avvisi).

Il modo corretto di implementare l'operatore di assegnazione (o il modo più comunemente accettato). È quello di utilizzare la copia e di swap idioma:

// 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, il mio professore l'ha scritto in classe :) Ma con quello che hai scritto, ci sono 0 avvertenze e il codice funziona bene. Grazie! – wrongusername

+1

Non è sempre possibile considerare tutti gli avvisi come errori. Troppo spesso ci sono intestazioni di sistema di cui hai bisogno (specialmente le più oscure) che producono sempre degli avvisi quando vengono usati, nonostante funzionino bene. Il modo migliore di procedere in tali situazioni è mettere in quarantena l'utilizzo dell'intestazione offendente in un singolo file sorgente in modo che almeno il resto del codice possa essere privo di avvisi. –

+0

@wrongusername @Martin: l'unica volta che usi l'idioma copy-and-swap è quando devi scrivere i Big Three. 'Location' in realtà ha bisogno di un operatore di assegnazione personalizzato? (Dal momento che non stai gestendo nulla, ne dubito!) – GManNickG

3

Hai controllato il codice che causa questa anomalia? I Heisenbug fenomeni citati qui:

Un esempio comune è un errore che si verifica in un programma che è stato compilato con un compilatore ottimizzato, ma non nello stesso programma se compilato senza ottimizzazione (ad esempio, per generare un debug-mode versione)

Ecco alcune linee guida: condizione

  • Race? stai usando discussioni?
  • Bordo di overflow del puntatore da qualche parte?
  • eseguire il codice attraverso valgrind per monitorare eventuali/cambiamenti erratici insoliti nel buffer di memoria da qualche parte

Un'altra citazione:

Un motivo comune per il comportamento heisenbug simile è che l'esecuzione di un programma in di debug la modalità pulisce spesso la memoria prima dell'avvio del programma e forza le variabili sulle posizioni dello stack, invece di tenerle nei registri. Queste differenze nell'esecuzione possono alterare l'effetto dei bug che coinvolgono l'accesso dei membri fuori limite o ipotesi errate riguardo ai contenuti iniziali della memoria. Un altro motivo è che i debugger di solito forniscono orologi o altre interfacce utente che causano l'esecuzione di codice aggiuntivo (come i metodi di accesso alle proprietà), che a sua volta può modificare lo stato del programma. Ancora un altro motivo è un fandango sul core, l'effetto di un puntatore che corre fuori limite. In C++, molti heisenbug sono causati da variabili non inizializzate.

Assicurarsi che gli interruttori sono spenti - nessuna ottimizzazione, informazioni complete di debug, deselezionare qualsiasi costruisce esistente, riavviare l'IDE e ricompilare di nuovo ....

+2

+1 per valgrind. (Picchiami anche io.) –

+0

Nessun thread: non ho ancora imparato a usarli. Verificherò valgrind, ma come faccio a sapere se ci sono cambiamenti insoliti? E cos'è un overflow del puntatore? Ho abilitato tutti gli avvisi sul compilatore e non vedo nessuna casella di ottimizzazione selezionata. Grazie! – wrongusername

+0

@wrongusername: quale compilatore stai usando? – t0mm13b

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

Questo restituisce un riferimento. Quando la funzione ritorna, cosa succede a other? (Muore, e non ti riferisci a nulla.) Poiché questa è la classe che hai a che fare intorno all'area problematica, questa è probabilmente la causa. La riorganizzazione del codice circostante lascia lo stack in una determinata condizione in cui si fa riferimento alla variabile morta "funziona".

Sostituirlo con return *this o semplicemente rimuovere completamente il controllo. (Assegnazione di due variabili, senza un ramo sarà probabilmente sempre correre più veloce che l'aggiunta di un ramo, su una CPU moderna.)

(si dovrebbe anche generalmente la parametro di riferimento, invece che per valore.)

+1

Il test 'this == & other' sarà SEMPRE falso, poiché' other' sarà sempre un temp allocato nello stack. Quindi questo codice, anche se povero, non è probabilmente il problema. Per correggere l'avviso, estrai il test e torna presto. –

+0

@ Chris: Ah, buon punto. – GManNickG

+0

Quindi il problema era che l'operatore di assegnazione dell'OP stava restituendo un riferimento alla variabile locale (parametro call by value). È corretto? – Chubsdad

1

Prima di tutto, la posizione :: operator = dovrebbe essere come questo, invece:

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

tuttavia, che, probabilmente, non spiega l'incidente. Puntatori cattivi sullo stack qui non si bloccano sulla maggior parte delle architetture (supponendo che x e y siano int).

Ora, questo è un mandelbug, non un heisenbug.Hai qualcun altro da qualche parte che corrompe la memoria. In bocca al lupo.

Problemi correlati