2009-03-02 11 views
1

Ich arbeite an einem Spiel und arbeite gerade an dem Teil, der Eingaben verarbeitet. Drei Klassen sind hier beteiligt, es gibt die ProjectInstance Klasse, die den Level startet und so, es gibt einen GameController, der den Eingang behandelt, und einen PlayerEntity, der von den Steuerelementen beeinflusst wird, wie durch die GameController bestimmt. Nach dem Start der Ebene erstellt die ProjectInstance die GameController, und sie ruft ihre Methode EvaluateControls in der Step-Methode auf, die innerhalb der Spielschleife aufgerufen wird. Die EvaluateControls Methode sieht ein bisschen wie folgt aus:Zeiger auf mysteriöse Weise auf Null zurücksetzen

void CGameController::EvaluateControls(CInputBindings *pib) { 
    // if no player yet 
    if (gc_ppePlayer == NULL) { 
     // create it 
     Handle<CPlayerEntityProperties> hep = memNew(CPlayerEntityProperties); 
     gc_ppePlayer = (CPlayerEntity *)hep->SpawnEntity(); 
     memDelete((CPlayerEntityProperties *)hep); 
     ASSERT(gc_ppePlayer != NULL); 
     return; 
    } 

    // handles controls here 
} 

Diese Funktion wird aufgerufen, korrekt und die Assertion nie auslöst. Bei jedem Aufruf dieser Funktion wird gc_ppePlayer jedoch auf NULL gesetzt. Wie Sie sehen können, ist keine lokale Variable außerhalb des Gültigkeitsbereichs. Die einzige Stelle gc_ppePlayer kann auf NULL gesetzt werden, ist im Konstruktor oder möglicherweise im Destruktor, von denen keiner zwischen den Aufrufen an EvaluateControls aufgerufen wird. Beim Debuggen erhält gc_ppePlayer vor der Rückgabe einen korrekten und erwarteten Wert. Wenn ich F10 noch einmal drücke und der Cursor auf der schließenden Klammer steht, ändert sich der Wert zu 0xffffffff. Ich bin hier ratlos, wie kann das passieren? Jemand?

Antwort

2

Debuggen Sie eine Release- oder Debug-Konfiguration? In der Release-Build-Konfiguration ist das, was Sie im Debugger sehen, nicht immer wahr. Es werden Optimierungen vorgenommen, und dies kann dazu führen, dass das Überwachungsfenster skurrile Werte anzeigt, wie Sie es sehen.

Sie sehen tatsächlich die ASSERT-Triggerung? ASSERTs werden normalerweise aus Release-Builds kompiliert, daher vermute ich, dass Sie einen Release-Build debuggen, weshalb das ASSERT die Anwendung nicht zum Beenden bringt.

Ich würde empfehlen, eine Debug-Version der Software zu erstellen und dann zu sehen, ob GC_ppePlayer wirklich NULL ist. Wenn es wirklich ist, sehen Sie möglicherweise eine Speicher-Heap-Beschädigung irgendeiner Art, wo dieser Zeiger überschrieben wird. Aber wenn es Speicherkorruption wäre, wäre es im Allgemeinen viel weniger deterministisch, als Sie beschreiben.

Nebenbei wird die Verwendung von globalen Zeigerwerten wie dieser allgemein als schlechte Praxis angesehen.Sehen Sie, ob Sie dies durch eine Singleton-Klasse ersetzen können, wenn es sich wirklich um ein einzelnes Objekt handelt, das global zugänglich sein muss.

+0

+1 für den ersten Teil, was ich gerade sagen wollte, aber ein Singleton ist genauso schlecht wie globale Daten (anti- Muster in meinem Buch) eine einfache Instanziierung und Referenz/Zeiger ist modularer. –

+0

@Robert - Ich denke Singletons können Anti-Muster sein, wenn sie missbraucht werden, was oft der Fall ist. Wenn ein Objekt wirklich ein EINZIGES Objekt im System ist und es niemals legal mehr als 1 von ihnen geben kann, ist das Singleton-Muster nicht das am besten geeignete? Besser als ein roher Zeiger/Referenz .. – LeopardSkinPillBoxHat

+0

..IMHO, denn zumindest können Sie Zugriffe darauf verfolgen. – LeopardSkinPillBoxHat

5

Setzen Sie einen Überwachungspunkt auf gc_ppePlayer == NULL, wenn der Wert dieses Ausdrucks ändert (auf NULL oder von NULL) der Debugger wird Sie genau dorthin zeigen, wo es passiert ist.

Versuchen Sie das und sehen Sie, was passiert. Suchen Sie nach nicht abgeschlossenen Strings oder Mempcy-Kopieren in den Speicher, der zu klein usw. ist. Dies ist normalerweise die Ursache für das Problem, dass globale/stack-Variablen zufällig überschrieben werden.

Um einen Beobachtungspunkt in VS2005 (Anweisungen brone)

  1. Zum Fenster Haltepunkte
  2. Klicken Sie auf Neu,
  3. Klicken Sie auf Datenhaltepunkt hinzufügen. Geben Sie
  4. &gc_ppePlayer in Adressfeld ein, lassen Sie andere Werte allein.
  5. Dann laufen.

Wenn gc_ppePlayer Änderungen, Breakpoint wird getroffen werden. - brone

+0

Hmm, wo ich das genau tun? Ich habe einen Watch-Dialog, aber ich denke nicht, dass du das meinst. Ich benutze Visual Studio 2005. – Aistina

+0

Ich habe keine Erfahrung in VS aber jeder Debugger wert ist in der Lage sein, Watch-Punkte (essential ein Break-Point auf Daten, die auf die Änderung des Wertes ausgelöst wird) in der Lage sein. Vielleicht ist Ihre nächste Frage, wie man einen Watchpoint in VS2005 setzt;) – hhafez

+0

@hhafez - Ich wusste nicht, dass es möglich war zu erkennen, wenn sich ein Wert in VS2005 ändert. Ich dachte, du musst nur Haltepunkte an verschiedenen Schlüsselpositionen setzen und dann sehen, ob sich der Wert auf den gewünschten Wert geändert hat (du kannst dies auch mit bedingten Haltepunkten erreichen). – LeopardSkinPillBoxHat

2

Mein erster Gedanke ist zu sagen, dass SpawnEntity() einen Zeiger auf ein internes Mitglied zurückgibt, das "gelöscht" wird, wenn memDelete() aufgerufen wird. Es ist mir nicht klar, wenn der Zeiger auf 0xffffffff gesetzt ist, aber wenn es während des Aufrufs von memDelete() auftritt, dann erklärt dies, warum Ihr ASSERT nicht feuert - 0xffffffff ist nicht dasselbe wie NULL.

Wie lange ist es her, seit Sie die gesamte Codebasis wiederhergestellt haben? Ich habe immer wieder Speicherprobleme wie diese gesehen, die durch einfaches Umbauen der gesamten Lösung aufgeklärt werden.

Haben Sie versucht, am Ende der Funktion einen Schritt in (F11) anstelle des Schrittes (F10) zu machen? Obwohl in Ihrem Beispiel keine lokalen Variablen angezeigt werden, haben Sie vielleicht einige der Einfachheit halber weggelassen. Wenn dies der Fall ist, wird F11 (hoffentlich) in die Destruktoren für eine dieser Variablen eintreten, so dass Sie sehen können, ob einer von ihnen das Problem verursacht.

+0

+1. Dies scheint die wahrscheinlichste Erklärung zu sein. –

+0

Die einzigen Variablen in dieser Funktion befinden sich unterhalb der Rückkehr, sodass ihre Destruktoren nicht aufgerufen werden. Wie auch immer, die Kommentare im verwendeten SDK sagen, dass CreateEntity die Entity an ihre Eigenschaften bindet (zur internen Verwendung im Editor), und SpawnEntity ist separator, um Entitäten während des Spielens zu erzeugen. – Aistina

+0

Was passiert, wenn Sie den Anruf vorübergehend zu memDelete() entfernen? Bleibt der Zeiger intakt? –

0

Sie haben einen "Fandango auf Kern".

Die dynamische Initialisierung überschreibt sortierte Bits des Speichers.

Entweder direkt oder indirekt wird global überschrieben. Wo ist das globale im Speicher relativ zum Heap?

Binär zerhacken den dynamisch initialisierten Teil, bis das Problem verschwindet. (auskommentieren zur Hälfte rekursiv)

0

Je nachdem, auf welcher Plattform Sie sind, gibt es Tools (kostenlos oder kostenpflichtig), die diese Art von Speicherproblem schnell herausfinden können.

Aus der Spitze von meinem Kopf:

Verwandte Themen