views:

326

answers:

5

Hi everyone,

I'm working on a game and I'm currently working on the part that handles input. Three classes are involved here, there's the ProjectInstance class which starts the level and stuff, there's a GameController which will handle the input, and a PlayerEntity which will be influenced by the controls as determined by the GameController. Upon starting the level the ProjectInstance creates the GameController, and it will call its EvaluateControls method in the Step method, which is called inside the game loop. The EvaluateControls method looks a bit like this:

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
}

This function is called correctly and the assert never triggers. However, every time this function is called, gc_ppePlayer is set to NULL. As you can see it's not a local variable going out of scope. The only place gc_ppePlayer can be set to NULL is in the constructor or possibly in the destructor, neither of which are being called in between the calls to EvaluateControls. When debugging, gc_ppePlayer receives a correct and expected value before the return. When I press F10 one more time and the cursor is at the closing brace, the value changes to 0xffffffff. I'm at a loss here, how can this happen? Anyone?

+5  A: 

set a watch point on gc_ppePlayer == NULL when the value of that expression changes (to NULL or from NULL) the debugger will point you to exactly where it happened.

Try that and see what happens. Look for unterminated strings or mempcy copying into memory that is too small etc ... usually that is the cause of the problem of global/stack variables being overwritten randomly.

To add a watchpoint in VS2005 (instructions by brone)

  1. Go to Breakpoints window
  2. Click New,
  3. Click Data breakpoint. Enter
  4. &gc_ppePlayer in Address box, leave other values alone.
  5. Then run.

When gc_ppePlayer changes, breakpoint will be hit. – brone

hhafez
Hmm, where do I do that exactly? I have a Watch dialog, but I don't think that's what you mean. I'm using Visual Studio 2005.
Aistina
I don't have experience in VS but any debugger worth its salt should be able to set watch points (essentiall a break point on data that gets triggered on the change of the value). May be your next question is how to set a watchpoint in VS2005 ;)
hhafez
@hhafez - I didn't know it was possible to detect when a value changes in VS2005. I thought you just had to set breakpoints at various key locations and then see if the value has changed to the desired value (you can also achieve this with conditional breakpoints).
LeopardSkinPillBoxHat
In Visual Studio, you'll do this by setting a conditional breakpoint. See http://support.microsoft.com/kb/308469.
Matt Davis
In VS right click on the right gutter. The option to set a conditional breakpoint will appear.
Robert Gould
You *can* use the memory window to see when values change, but it can be tedious. Open the memory window and enter the address of the gc_ppePlayer pointer. As you step through your function, the value of the pointer in the hex window will turn red if the value changes...
Matt Davis
@Matt - Yes, conditional breakpoints will work, but you still need to set the breakpoint at a specific location in the code which you know will run. Different from what hhafez seems to be proposing, which allows you to detect when a value changes ANY time rather than at a specific code location.
LeopardSkinPillBoxHat
...This allows you to see which line the change occurred on. If the change occurs on a line with a function call, you will need to re-run your program and step into that line to further isolate exactly where the change is occurring.
Matt Davis
does this mean there is no watchpoints in VS like there in gdb? I'd be very surprised if that is the case.
hhafez
brone
A response whilst I composed... Data Breakpoints are what other things (Code Warrior, gdb) call Watchpoints. Incredibly useful when you can use them (which isn't always, since the address to watch has to be known ahead of time).
brone
thanks brone, that is essentially the watch point is, I'll add your instructions to my answer
hhafez
+2  A: 

Are you debugging a Release or Debug configuration? In release build configuration, what you see in the debugger isn't always true. Optimisations are made, and this can make the watch window show quirky values like you are seeing.

Are you actually seeing the ASSERT triggering? ASSERTs are normally compiled out of Release builds, so I'm guessing you are debugging a release build which is why the ASSERT isn't causing the application to terminate.

I would recommend build a Debug version of the software, and then seeing if gc_ppePlayer is really NULL. If it really is, maybe you are seeing memory heap corruption of some sort where this pointer is being overridden. But if it was memory corruption, it would generally be much less deterministic than you are describing.

As an aside, using global pointer values like this is generally considered bad practice. See if you can replace this with a singleton class if it is truly a single object and needs to be globally accessible.

LeopardSkinPillBoxHat
+1 for the first part, just what I was going to say, but a Singleton is just as bad as global data (anti-pattern in my book) a simple instantiation and reference/pointer is more modular.
Robert Gould
@Robert - I think singletons can be anti-patterns when they are misused, which is often the case. If an object is truly a SINGLE object in the system, and there can never legally be more than 1 of them, isn't the singleton pattern the most suitable one to use? Better than a raw pointer/reference..
LeopardSkinPillBoxHat
..IMHO, because at least you can track accesses to it.
LeopardSkinPillBoxHat
Yes, this was indeed the problem. I was using Release because Debug wouldn't start, but when I (finally) solved that problem, it turned out you were quite right. gc_ppePlayer doesn't have a valid value at all. Now to find out why :-p
Aistina
+2  A: 

My first thought is to say that SpawnEntity() is returning a pointer to an internal member that is getting "cleared" when memDelete() is called. It's not clear to me when the pointer is set to 0xffffffff, but if it occurs during the call to memDelete(), then this explains why your ASSERT is not firing - 0xffffffff is not the same as NULL.

How long has it been since you've rebuilt the entire code base? I've seen memory problems like this every now and again that are cleared up by simply rebuilding the entire solution.

Have you tried doing a step into (F11) instead of the step over (F10) at the end of the function? Although your example doesn't show any local variables, perhaps you left some out for the sake of simplicity. If so, F11 will (hopefully) step into the destructors for any of those variables, allowing you to see if one of them is causing the problem.

Matt Davis
+1. This seems to be the most likely explanation.
Mitch Wheat
The only vars in this function are below the return, so their destructors would not get called. Anyway, the comments in the SDK I'm using say that CreateEntity ties the entity to its properties (for use internally in the editor), and SpawnEntity is seperator, for creating entities while playing.
Aistina
What happens if you temporarily remove the call to memDelete()? Does the pointer remain intact?
Matt Davis
A: 

You have a "fandango on core."

The dynamic initialization is overwriting assorted bits (sic) of memory.

Either directly, or indirectly, the global is being overwritten. where is the global in memory relative to the heap?

binary chop the dynamically initialized portion until the problem goes away. (comment out half at a time, recursively)

Tim Williscroft
A: 

Depending on what platform you are on there are tools (free or paid) that can quickly figure out this sort of memory issue.

Off the top of my head:

TofuBeer