Skip to main content
gamedev.net
🔒 Locked

Virtual function problem

Started by BSVino 11 replies 2.1k views
Original Post
BSVino
BSVino
I have a group of virtual functions with identical signatures in a class:

	virtual float				HealthRechargeRate() const { return 0.2f; };	virtual float				ShieldRechargeRate() const { return 1.0f; };	virtual float				GetTankSpeed() const { return 2.0f; };	virtual float				TurnPerPower() const { return 45; };	virtual float				GetMaxRange() const { return 70.0f; };	virtual float				GetMinRange() const { return 50.0f; };	virtual float				GetTransitionTime() const { return 2.0f; };


They work fine in most cases, except in my terrain rendering file, terrain.cpp. If they are used anywhere in terrain.cpp, if you call one of them it calls the next one instead. For example, if I call GetMaxRange() the debugger will step into GetMinRange() instead. If I call GetTransitionTime() it just crashes. It returns the other function's value so I know it's not just the debugger messing up, and it's causing visual bugs in my terrain rendering procedures. There are other virtual functions in this class but they all work fine, it's only this block of functions that behave improperly. Moving them somewhere else in the class definition doesn't help. Moving the inline function definition into the class's .cpp file doesn't help. Making them not virtual fixes the problem, but obviously I need them to be virtual. The problem doesn't occur in any other files. I also looked in the vftable through VS's debugger, and didn't see any of these functions present for some reason.

I thought it was some kind of strange memory overwriting problem, but then I examined the disassembler. In every file where the functions behave correctly, the virtual function call for GetMaxRange() looks like this:

006FDA34  mov         eax,dword ptr [edx+94h] 


But for terrain.cpp it looks like this:

006FDA34  mov         eax,dword ptr [edx+98h] 


It's four bytes off. So it's definitely a compile-time problem and not a runtime problem. I've examined the preprocessor generated file (the .i file) and it has the correct call in it so I don't think it's a preprocessing problem. The problem didn't always occur, it started when I added some networking into the game, which involved some extensive changes to the way objects are created and destroyed.

Any ideas?
fng
fng
Could it be that you include a wrong header file? I had a similar problem until I figured out I had messed up my include paths and an old header, which was missing a newer member variable, was used in a particular .cpp compilation.
-- blog: www.fysx.org
haegarr
haegarr
Do you have made a total clean and re-build of the project? Maybe some dependencies are not working correctly.
BSVino
BSVino
Yeah I've done many rebuilds. It happens in debug and release modes.

fng, I had done some fiddling with the header file order but after your suggestion I did more and I discovered:

#include <GL/freeglut.h>#include "digitanks/digitank.h"


Broken.

#include "digitanks/digitank.h"#include <GL/freeglut.h>


Works.

I'd like to get to the bottom of the issue and not just avoid it by keeping the one header before the other all the time... what about freeglut could be causing this? Maybe the windows headers that freeglut includes? I'm always running into problems because of those damn headers.

edit: Yup, definitely the windows headers, some careful tests have shown that including windows.h will cause the problem while not including it won't. Hmmmm
Adam_42
Adam_42
The issue will probably be that windows.h uses #defines for most of the function names to handle the difference between unicode and non-unicode. Because they are #defines they will also rename your functions and variables that happen to have the same name as a windows function.

If it gets renamed in one case, and not in another it can cause problems, although it's usually just linker errors.

In your case it's not linker errors that you're getting because the function bodies are in the .h file. I'd suggest moving them into the .cpp file as virtual functions generally won't be inlined anyway.
BSVino
BSVino
Like I said in the OP, I tried that and it didn't work. Also I mentioned in the OP that I looked in the preprocessed file and it retained all of my original function names.

Since it's a windows.h problem and I don't use windows.h I'm just going to avoid including windows.h if I can. I intend to switch away from freeglut soon anyway. This wouldn't be the first time windows.h has caused me problems.
Antheus
Antheus
Quote:
Original post by BSVino

It's four bytes off. So it's definitely a compile-time problem and not a runtime problem. I've examined the preprocessor generated file (the .i file) and it has the correct call in it so I don't think it's a preprocessing problem. The problem didn't always occur, it started when I added some networking into the game, which involved some extensive changes to the way objects are created and destroyed.


Are you sure you didn't leave off const, use invalid type or do something equally silly, have a buffer overrun corrupting vtable, call deleted instances. Are these types globals?

The chance of MSVC (aka widely used) compiler borking something as trivial as virtual function call are very slim.

As a simple test, put an int or a sequence of chars initialized to some specific value, and cleared in destructor as the very first variable of your base class. Then, check if this value is correct when calling these functions.

Quote:
It's four bytes off
That's fine, v-tables are unique to each class, at very least, compiler is free to do whatever it wants with them.

Or, at very least, post complete signature of base class as well as terrain class (that means full class declaration with only bodies of functions omited).
BSVino
BSVino
Hmm. I agree that the windows header itself probably isn't the direct cause, but I don't quite get how you come to the conclusion that it's a runtime problem. The compiler may be free to do whatever it wants to with the vtables, but shouldn't it do it consistently for a given class signature so that things like this don't happen? Also, I don't get how that would account for this behavior:

in terrain.cpp:
void CTerrain::Render(){    CDigitank* pTank = GetTank();    pTank->GetMaxRange(); // Calls GetMinRange()    pTank->Test();}


in digitank.cpp:
CDigitank* GetTank(){    CDigitank* pTank = whatever;    pTank->GetMaxRange(); // Calls GetMaxRange()    return pTank;}void CDigitank::Test(){    pTank->GetMaxRange(); // Calls GetMaxRange()}


So, the problem only happens in terrain.cpp. If it were memory corruption then all of those calls would be GetMinRange(). Doesn't make sense to be memory corruption when it works as soon as the call stack leaves that one file again.

(And yes, I swear this is exactly what it does.)
alvaro
alvaro
Are you casting pointers in some unsafe way at some point?

If you still can't find the problem, it might be time to make a copy of your project and start removing everything you can while preserving the bug. Usually these things can be distilled down to around 20 lines of code, and then you can post those for everyone to analyze and to test for themselves.

Captain P
Captain P
How do you verify which function is called?


Either way, I probably can't offer a solution, but perhaps I can give some useful design suggestions. I assume that each tank class has it's own implementation of those virtual functions, right? If so, why not create a TankClass class, with plain member variables, and for each tank type, create an instance of it, filled with appropriate values? That should make it easy to add new tank types, even to make types data-driven. If I understand your games design correctly, then such a flyweight pattern approach seems quite suitable.
BSVino
BSVino
That's an interesting thought, Cap. At the moment my design only calls for three tank types so I'm not really wanting to create bunches of tank classes on the fly, so I'm doing it the simple way here, but I think you're right that if I wanted to create many different kinds of tanks, that would be the way to go.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.