Advertisement

Very strange memory leaks

Started by May 05, 2005 08:46 AM
9 comments, last by WitchLord 19 years, 6 months ago
Hi! This will be a longish post :-) I've found 2 very puzzling memory leaks. The first one seems to have something to do with strings and temporary objects. Here's my code:

// class code
struct GridFont {

   GridFont()
   : strName("")
   , dSize(0.0)
   , bIsBold(false)
   , bIsItalic(false)
   {
   }

   GridFont(const string& name, double size, bool isBold, bool isItalic)
   : strName(name)
   , dSize(size)
   , bIsBold(isBold)
   , bIsItalic(isItalic)
   {
   }

   void Constructor() {
      new(this) GridFont;
   }

   void Constructor(const string& name, double size, bool isBold, bool isItalic) {
      new(this) GridFont(name, size, isBold, isItalic);
   }

   GridFont& operator=(const GridFont& other) {

      strName    = other.strName;
      dSize      = other.dSize;
      bIsBold    = other.bIsBold;
      bIsItalic   = other.bIsItalic;

      return *this;
   }

   string  strName; 
   double  dSize;
   bool   bIsBold;
   bool   bIsItalic;
};


// registration code
r = pEngine->RegisterObjectType("GridFont", sizeof(GridFont), asOBJ_CLASS_CA); assert(r >=0);

r = pEngine->RegisterObjectBehaviour("GridFont", asBEHAVE_CONSTRUCT, "void Constructor()", asMETHODPR(GridFont, Constructor, (), void), asCALL_THISCALL); assert(r >= 0);
r = pEngine->RegisterObjectBehaviour("GridFont", asBEHAVE_CONSTRUCT, "void Constructor(const string∈, double, bool, bool)", asMETHODPR(GridFont, Constructor, (const string&, double, bool, bool), void), asCALL_THISCALL); assert(r >= 0);
r = pEngine->RegisterObjectBehaviour("GridFont", asBEHAVE_ASSIGNMENT, "GridFont& f(const GridFont∈)", asMETHOD(GridFont, operator=), asCALL_THISCALL); assert(r >= 0);

r = pEngine->RegisterObjectProperty("GridFont", "string strName", offsetof(GridFont, strName)); assert(r >=0);
r = pEngine->RegisterObjectProperty("GridFont", "double dSize", offsetof(GridFont, dSize)); assert(r >=0);
r = pEngine->RegisterObjectProperty("GridFont", "bool bIsBold", offsetof(GridFont, bIsBold)); assert(r >=0);
r = pEngine->RegisterObjectProperty("GridFont", "bool bIsItalic", offsetof(GridFont, bIsItalic)); assert(r >=0);



// script code
void main() {

for (int i = 0;; ++i) {
   GridFont gridFont2("4", 10.0, true, false);
}
I'm using stdstring wrapper (from AS 2.0) If I don't use the for loop, VC6 will report a memory leak for "4". If I use the infinite for loop, task manager will confirm the leak. Interestingly, VS.NET 2003 will report a leak for identical code only if the string is 16 characters or longer. For shorter strings, no leak is detected (and Task Manager confirms it) I found the other memory leak when I switched to using the new scriptstring wrapper. My main project is linked with MT DLL CRT, and I use AS DLL. While I was using stdstring wrapper, I could link AS DLL with MT CRT (static), since no memory buffer allocated in AS DLL was ever deleted in my code. But, since scriptstring deletes buffers allocated by AS DLL, I had to link AS DLL with MT DLL CRT library (as it should have been in the first place, but I forgot to check :-) Now, when I run a script (can be an emtpy 'void foo()' function), VS6 reports 2 memory leaks, and VS.NET 2003 reports 3. Here's a sample:

// VS6
{4953} normal block at 0x00CDBEA0, 4 bytes long.
 Data: <8 7 > 38 8A 37 00 
{4952} normal block at 0x00CDBE48, 24 bytes long.
 Data: <                > A0 BE CD 00 00 00 00 00 01 00 00 00 00 00 00 00 

// VS.NET 2003
{5175} normal block at 0x0131BBE8, 4 bytes long.
 Data: <  1 > 18 BD 31 01 
{5174} normal block at 0x0131BB90, 24 bytes long.
 Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 CD CD CD 
{5173} normal block at 0x0132AB10, 24 bytes long.
 Data: <  1             > E8 BB 31 01 00 00 00 00 01 00 00 00 00 00 00 00 
These are one-time leaks. I can repeatedly discard modules from the engine, build and execute scripts, there will only be these 2/3 leaks reported. I release the context and engine pointers on exit. It seems that the leak originates in Build function (I commented out calls in reverse order until memory leak disappeared: execute, setargument, prepare, build :) If I use strings as in the previous sample, those are reported, too. I'll try to make a minimal example tomorrow to see if I can repeat it.
I'll try to reproduce the problem, but it could be difficult. If you can send me a small project that reproduces the problem it would be much easier for me.

When using DLLs you need to be careful to make sure that all memory is released in the same module it was allocated. That is, if the DLL calls 'new' to allocate some memory it must also be the DLL that calls 'delete' for that memory.

With AngelScript you can make this task easier by registering the behaviours asBEHAVE_ALLOC and asBEHAVE_FREE (using the same syntax as malloc() and free()).

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement
Ok... I've created a small sample project. You can download it here: AngelLeak.zip

I've packed AS with it (this is the way my project's code is maintained), there are both DLL and LIB versions (just rename the dir you wish to use) Project files for both VS6 and VS 2003 are available.

Test class has quite a few functions (this is used to demonstrate another problem I've found, but more about that later :) For memory leak check you can remove all exported functions except Foo00.

It seems that the small fixed leak is the problem in my project, since it doesn't appear here. But, string leaks are here (the pattern I've described in the original post) I've tried using both DLL and LIB versions of AS, but there was no difference (as expected) If you place the call to Foo00 in script's main function into infinite loop, you can watch the memory consumption go up in the Task Manager.

Another problem that I've fonund probably has something to do with VC6 compiler, and I'm not sure if you can fix it... If you use a class that registers a large number of functions (like Test), and a script calls functions that are registered towards the bottom (like Foo60), the application will crash with an access violation in Release mode if optimization is set to 'maximize speed'. If the optimization flag is set to 'default', there is no crash (over-optimization will do that to you :) If you move function registration a bit further up in the source code, there is no crash (but some other function call may crash the app)

This problem has been confirmed on two boxes, and it doesn't appear with VC.NET 2003 compiler.

Please note that I'm leaving on a vacation today and I'll be gone for two weeks, so I won't be able to read the forum...

See you later people. Have fun.

P.S. About memory allocation/deallocation in DLLs. You can allocate memory in one module and deallocate it in another, but both modules must be linked with the same version of CRT library.
I found your memory leak. And I'm pleased to say that it wasn't a bug in AngelScript :)

The problem is that AngelScript never calls the TheStruct's destructor, since the destructor behaviour was never registered. Sure, TheStruct doesn't have a destructor explicitly declared, but C++ automatically generates one to free the string member. So we need to register the destructor anyway. I solved it like this:

struct TheStruct {  // Same as before  ...  // New method that will be registered with AngelScript  void Destructor() {    // Call the default destructor, even though none has been declared    this->~TheStruct();  }  // Same members as before  string strName;  int    nVal;};void Test::AngelScriptRegister(asIScriptEngine* pEngine) {  int r = 0;  r = pEngine->RegisterObjectType("TheStruct", sizeof(TheStruct), asOBJ_CLASS_CA); assert(r >=0);	  r = pEngine->RegisterObjectBehaviour("TheStruct", asBEHAVE_CONSTRUCT,	"void Constructor()",						asMETHODPR(TheStruct, Constructor, (), void),					asCALL_THISCALL); assert(r >= 0);  r = pEngine->RegisterObjectBehaviour("TheStruct", asBEHAVE_CONSTRUCT,	"void Constructor(const string∈, int)",	asMETHODPR(TheStruct, Constructor, (const string&, int), void),	asCALL_THISCALL); assert(r >= 0);  r = pEngine->RegisterObjectBehaviour("TheStruct", asBEHAVE_DESTRUCT,    "void Destructor()", asMETHOD(TheStruct, Destructor), asCALL_THISCALL); assert( r >= 0 );  r = pEngine->RegisterObjectBehaviour("TheStruct", asBEHAVE_ASSIGNMENT,	"TheStruct& f(const TheStruct∈)",			asMETHOD(TheStruct, operator=),									asCALL_THISCALL); assert(r >= 0);	  r = pEngine->RegisterObjectProperty("TheStruct", "string strName",		offsetof(TheStruct, strName));		assert(r >=0);  r = pEngine->RegisterObjectProperty("TheStruct", "int nVal",			offsetof(TheStruct, nVal));			assert(r >=0);  // Same as before  ...}


I'll look into the problem with the class with many methods at a later time. I've found that MSVC6 optimizations can at times change the result of a piece of code depending on the order of the statements/operators.

Thanks for taking the time to make the test code. It makes it so much easier for me to figure out the problem.

Oh, and have a nice vacation.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Wow, has anyone in this thread heard of [ source ] tags to keep formatting?
Yes, we have.

But I for some reason prefer to use the tag, except for very large code snippets.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement
I'm just going to point out that the reason small strings did not leak in vs2003 is that the string implementation in that release uses an on-stack buffer to optimize small strings. 6.0 does not.
Quote: Original post by Deyja
I'm just going to point out that the reason small strings did not leak in vs2003 is that the string implementation in that release uses an on-stack buffer to optimize small strings. 6.0 does not.


I'll risk a further but slight derailing of the topic: :)
I noticed that small std::strings show up as meaningful strings in the debug watch window only for small string values.. I wonder if this is due to the usage of the on-stack buffer... it took me a while to figure out my strings weren't getting corrupted the first time I used std::strings!
tIDE Tile Map Editorhttp://tide.codeplex.com
Yes. You can get a look at the contents of the string if it's longer though. Just follow the long and tangled web of pointers common to all stl code. :D Also theres a file somewhere that controls that stuff. If you change it though, then short ones won't show up properly.
I'm back! :-)

Yup. It's clear now. If your class has non-primitive members, you should register a destructor behaviour, since class' destructor calls members' destructors. In C++, it's all hidden, of course :)

Maybe we should put together some 'Class registration guidelines' doc to avoid these kind of problems. I've started writing a small cookbook for my colleagues (e.g. when using handles, you have to register an assignment operator that copies all members except the refcount variable), maybe we could expand it and put it in the docs section.

Optimization problem is low priority, we don't use optimizations in VC6, since it proved to be problematic :)

And I had a great vacation. Sailing from Croatia to Greece and back. Very nice :-)

This topic is closed to new replies.

Advertisement