evil optimizing due to empty destructor?

Started by
6 comments, last by GameDev.net 18 years ago
I've been looking at some of the assembly code output (full optimizations on) of VS2003 and VS2005 recently, and have found something bizarre with the way objects are created and destroyed. Given the following simple, innocent-looking class:
#define MYINLINE		__forceinline

class foo
{
public:
    MYINLINE foo();
    MYINLINE ~foo();
};

MYINLINE foo::foo()
{
}

MYINLINE foo::~foo()
{
}

One would expect any code which uses this object to simply not have any overhead with a constructor or destructor. Indeed, doing something as simple as:
void main(void)
{
    foo f;
}
Results in the compiler completely optimizing away f, as it's not even being used. Even if class foo is actually expanded to do something useful, the constructor and destructor are empty and are never called. However, things get a little strange when an array of foos are allocated:
void main(void)
{
    foo f[1024];
}
The compiled code gets turned into this evil mess:
void main(void)
{
00401020  sub         esp,400h 
	foo f[1024];
00401026  push        offset foo::~foo (401010h) 
0040102B  push        offset foo::foo (401000h) 
00401030  push        400h 
00401035  push        1    
00401037  lea         eax,[esp+10h] 
0040103B  push        eax  
0040103C  call        `eh vector constructor iterator' (401121h) 
}
00401041  push        offset foo::~foo (401010h) 
00401046  push        400h 
0040104B  push        1    
0040104D  lea         ecx,[esp+0Ch] 
00401051  push        ecx  
00401052  call        `eh vector destructor iterator' (4010BEh) 
00401057  xor         eax,eax 
00401059  add         esp,400h 
0040105F  ret              

Yuck! Both the constructors and destructor functions are being called for _each_ of the objects in the array (1024 times)! Interestingly, the use of __forceinline didn't even give warning 4714. Stepping into the constructor and destructor iterators is a loop which loads the 'this' pointer for the class and then calls into the actual ctor or dtor:
MYINLINE foo::foo()
{
00401000  mov         eax,ecx 
}
00401002  ret              

MYINLINE foo::~foo()
{
}
00401010  ret              

I then tried removing the destructor function body entirely, and finally the compiler optimized the prolog/epilog code away. Is there anything "wrong" with not having a destructor for an object? In my case where there is no code in the destructor, is it actually better to not declare one in the first place? Also, if anyone is willing, I'm curious to see how other compilers (gcc, Intel, etc) handle this situation.
Advertisement
Quote:Original post by bpoint
Yuck! Both the constructors and destructor functions are being called for _each_ of the objects in the array (1024 times)!


And just how do you propose creating an array of 1024 objects be done, if not by calling 1024 constructors?

If there is no body to the ctors/dtor, then there's no sense giving an actual definition, as the compiler will generate them anyway.

__forceinline is obviously nonstandard, and could be forcing some weird things to happen; as such, I have no idea what it's really supposed to do. I thought I read somewhere that constructors and destructors could not be inlined.
Quote:Original post by RDragon1
Quote:Original post by bpoint
Yuck! Both the constructors and destructor functions are being called for _each_ of the objects in the array (1024 times)!

And just how do you propose creating an array of 1024 objects be done, if not by calling 1024 constructors?


I understand that under normal circumstances that is how it is accomplished. My example is a bit of a special case -- but if the function body is indeed empty, I would prefer the optimizer to simply not call a function which just does a 'ret'. In other words, don't even bother calling the ctors or dtors like it did when a single object was instanced.

Quote:If there is no body to the ctors/dtor, then there's no sense giving an actual definition, as the compiler will generate them anyway.

But it seems that indeed does matter. As I said in my previous post: if I don't specify a dtor, then the optimizer works properly and doesn't call the ctors or dtors on the allocated array of objects.

Quote:__forceinline is obviously nonstandard, and could be forcing some weird things to happen

The standard "inline" made no difference. I merely used __forceinline in the sample because I was expecting to get a warning since it wasn't inlined -- and I didn't.
If a class has a destructor then it is not a POD type.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
the only time you ever need to write an empty constructor is when you want to modify it's visibility, e.g. make it private, to prevent uncontroller construction etc. Otherwise an empty constructor/desctructor is completely unnecessary as the default constructor etc is equivalent. So just don't have one in this case.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by iMalc
the only time you ever need to write an empty constructor is when you want to modify it's visibility, e.g. make it private, to prevent uncontroller construction etc.

The actual class that I originally started the optimization checking on actually has multiple constructors. Different parameters set different internal variables in the class, but the constructor with no parameters does no initialization at all.

Quote:Otherwise an empty constructor/desctructor is completely unnecessary as the default constructor etc is equivalent. So just don't have one in this case.

Well, unfortunately I need to have a constructor...

I'll just remove the destructor since that seems to be the only way to work around this issue.
but sometime u got to create an empty constructor,

because ...

if you are writing say a particle class, and u declare 1000 particle array,

your code will generate error without empty constructor, because the compiler needs a default constructor to begin with, right? so solution is

if u gonna create somany beeg arrays or even dynamically alloc them, try to avoid it. and i think u could use vector (stl) instead, in the stupid example i gave (the particle one) because then u could push_back a particle whenever u need it!
Some times ago I read on gdalgorithms-list Sam Martin's following statement in a thread about C and C++ style math libraries:

"- don't declare a destructor if you don't need one, which you won't need to in a standard vector/matrix class. If you do the compiler is likely to consider it non-trivial (even if it's empty) and greatly limit the amount of temporary removals it does. This is a consequent of the complexities of exception handling. This happens in both VC 2003 and 2005."

This topic is closed to new replies.

Advertisement