Jump to content
  • Advertisement
Sign in to follow this  
ApochPiQ

Did I just conquer the static order of initialization problem?

This topic is 3630 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been trying to come up with a sneaky way to get a good memory leak dump in a nontrivial code base. The main problem that's been cropping up is the presence of statically constructed objects - specifically, these objects aren't destructed until after main() exits, which means if I dump the memory contents at the end of main(), I see "leaks" which aren't actually leaking. The same goes if I try to use an object destructor, because of the static order of initialization problem. So I came up with a dirty little hack that seems to eliminate the problem:
class LastOneOut
{
public:
	LastOneOut()
	{
		if (RefCounter == 0) {
			std::wcout << "Lights on" << std::endl;
		}

		++RefCounter;
		std::wcout << "LAST ONE OUT CONSTRUCTED - " << RefCounter << std::endl;
	}

	~LastOneOut()
	{
		--RefCounter;
		std::wcout << "LAST ONE OUT DESTRUCTED - " << RefCounter << std::endl;

		if (RefCounter == 0) {
			std::wcout << "Last one out gets the lights!" << std::endl;
		}
	}

protected:
	static int RefCounter;
};

The trick is to place a LastOneOut sentinel object as the first member variable of each class which has a static object created. In other words, if I have a Foo class which is global, I drop a LastOneOut member as its first member, and voila - everything works. Now, this relies on the fact that the first declared member gets constructed first, and therefore destructed last. This avoids the issue of having sibling members who get destructed before the sentinel object does. So here's my question: is that behaviour guaranteed by the standard, or is it just coincidence that VC++ 2005 happens to work that way? If this is reliable behaviour, I just found a really nice way to trap actual memory leaks. If not... well, back to the drawing board, I suppose [smile]

Share this post


Link to post
Share on other sites
Advertisement
C++ standard, 12.6.2.5:
Quote:

Initialization shall proceed in the following order:

  • First, and only for the constructor of the most derived class as described below, virtual base classes shall be initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where "left-to-right" is the order of appearance of the base class names in the derived class base-specifier-list.

  • Then, direct base classes shall be initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

  • Then, nonstatic data members shall be initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

  • Finally, the body of the constructor is executed.

[Note: the declaration order is mandated to ensure that base and members subobjects are destroyed in the reverse order of initialization. ]


So yes, your members are initialized in the order they are declared and destroyed in that reverse order.

Share this post


Link to post
Share on other sites
Here's my suggestion:


struct Context {
EvilGlobalObject a;
MustComeLast b;

Context(): a(args), b(other args) {}
} *g_Context;

void main(int argc, char** argv) {
{
Context c;
g_Context = &c;
doInterestingThings(); // the rest of the code refers to g_Context->a and b.
}
checkForMemoryLeaks();
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
Here's my suggestion:

*** Source Snippet Removed ***


Consider me stupid for a moment - what advantages does that offer? In my current situation I need something that can easily be surgically inserted into a very large codebase; adding a single member variable to a handful of classes is a trivial change and doesn't require rewriting anything; whereas your method would mean we can no longer refer to globals directly but have to go through the Context interface.

Share this post


Link to post
Share on other sites
Quote:
Original post by ApochPiQ
Quote:
Original post by Zahlman
Here's my suggestion:

*** Source Snippet Removed ***


Consider me stupid for a moment - what advantages does that offer? In my current situation I need something that can easily be surgically inserted into a very large codebase; adding a single member variable to a handful of classes is a trivial change and doesn't require rewriting anything; whereas your method would mean we can no longer refer to globals directly but have to go through the Context interface.


I was hoping your system has more classes than it does references to globals. ;) It's a way of dealing with the SOI problem in general. (You could also redo various key functions as member functions of the Context, including a root doInterestingThings() - or perhaps operator()? - and then the "globals" would be in scope automatically as class members.) After all, the constructor of Context gives you a quite unambiguous way to set the SOI.

Share this post


Link to post
Share on other sites
Ah ok. I'm familiar enough with the wrap-with-function idiom (as discussed in the FAQ link above), but I guess what's going on here is actually a somewhat orthogonal problem. It's not a strict SOI issue since there aren't interdependencies between our globals (we're not that sloppy [wink]) but rather just a mechanism for guaranteeing that certain code runs at certain points.

In any case, since the standard seems to indicate that this is a safe thing to do, I think I'll go ahead with the original design. What we really need is a way to trap memory leaks, not a clean way to handle globals and SOI (since we do construct-on-first use for most of the stuff anyways).


Thanks all for the input.

Share this post


Link to post
Share on other sites
I got around this in my memory manager with #pragma init_seg(compiler) to put my static / global glass in thew compiler segment that is initialized first and destroyed last (STL objects are in the "lib" segment).

Share this post


Link to post
Share on other sites
Hmm, interesting. Only thing is it doesn't seem particularly portable, which is one of the bigger requirements of the hack. Nice trick, though!

Share this post


Link to post
Share on other sites
Quote:
Original post by ApochPiQ
...
The main problem that's been cropping up is the presence of statically constructed objects - specifically, these objects aren't destructed until after main() exits
...


Are they destructed before or after atexit's mechanism is triggered?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!