• Advertisement
Sign in to follow this  

c++ oo syntax behind the scenes behavior

This topic is 1378 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

if i declare a class, then declare an instance of that class, when is the instance allocated on the heap (assuming it has member variables) ?

 

where is the VMT stored, and when ?

 

and there's just one VMT per class, right?

 

non virtual method calls require no vmt lookup, correct?

 

 

 

 

Share this post


Link to post
Share on other sites
Advertisement

When you define/instantiate an instance of a class using the new operator, sizeof(ClassName) number of bytes are allocated on the heap, and a pointer pointing to the beginning of the instance is returned.

ClassName* instance = new ClassName(); // memory is allocated on the heap here
instance->doSomething();

When you define/instantiate an instance of a class on the stack, the memory is guaranteed to be allocated when you need it.

ClassName instance; // Memory is allocated on the stack here
instance.doSomething();

The VMT is typically stored by adding an extra private pointer member variable to the class, which points to a virtual table. Every instance of the class generally shares the same virtual table.

 

If you add more virtual functions, the class size does not grow though.

 

This can be shown with the following code:

#include <iostream>

class Foo
{
public:
    Foo() {}
    virtual ~Foo() {}
private:
    int a;
};

class Bar
{
public:
    Bar() {}
    virtual ~Bar() {}
    virtual void set( int a ) { this->a = a; }
    virtual int get( int a ) { return a; }
private:
    int a;
};

int main()
{
    std::cout << "sizeof(Foo): " << sizeof(Foo) << std::endl;
    std::cout << "sizeof(Bar): " << sizeof(Bar) << std::endl;

    return 0;
}

 

Foo and Bar have the same size, even though Bar has more virtual functions.

Share this post


Link to post
Share on other sites


If you add more virtual functions, the class size does not grow though.

That can be a little misleading.  The size of objects (class instances) does not grow, but the vtable size changes, and maybe even its layout changes.  That's important to know, because it's an ABI change and the not infrequent cause of subtle and inexplicable runtime failures.

 

This sort of thing shows up as a problem if, for example, you add a virtual function to a header file and rebuild a program, but do not rebuild a DLL that also uses the header file.  Bam!

Share this post


Link to post
Share on other sites

I think you're confusing a pointer to the vtable and the vtable itself. An object will store a pointer to the vtable not the full table. The vtables themselves are generally stored in a read only data segment. 

Yep, already edited my post. I should remember to hold off on posting in the morning until after breakfast. laugh.png

Share this post


Link to post
Share on other sites


The instance is allocated on the heap when you call new (or malloc() or another similar func). No sooner, no later.

 

what about something like:

 

class myclass
etc
 
 
 
void main
{
myclass object1;   // declare an instance of a myclass object called object1
etc
}
 

 

note that my syntax may be off a bit there, but i think you get the type of declaration i'm talking about.

 

that would get allocated when main was called, right?

 

but something like....

 

 
class myclass
etc
 
myclass object1;   // declare an instance of a myclass object called object1, global to everything that follows in the module.
 
 
void main
{
etc
}
 

Share this post


Link to post
Share on other sites


When you define/instantiate an instance of a class on the stack, the memory is guaranteed to be allocated when you need it.
ClassName instance; // Memory is allocated on the stack here
instance.doSomething();

 

fascinating! i thought local declarations did a new in the background and a dispose on return, using the heap.

 

so locally declared objects go on the stack. that would imply no memory leaks possible, correct?

Share this post


Link to post
Share on other sites


This sort of thing shows up as a problem if, for example, you add a virtual function to a header file and rebuild a program, but do not rebuild a DLL that also uses the header file.  Bam!

 

the classic code and header out of sync explosion!

Share this post


Link to post
Share on other sites

based on the above posts, given:

 

 
class myclass
etc
 
void main
{
myclass object1;
}
 

 

would object1 go on the stack when main was called? or is main a special case and it goes in the data segment?

 

also, i takes it that given:

 

 
class myclass
etc
 
myclass object1;  // global to code that follows in this module
 
void main
{
etc
}
 
 

 

 

object1 (its member var storage space) would go in the data segment?

Share this post


Link to post
Share on other sites


 
Quote
non virtual method calls require no vmt lookup, correct?
Yes. And if the class itself has zero virtual functions, then it won't have any Virtual Method Table.

 

 

 

I think you're confusing a pointer to the vtable and the vtable itself. An object will store a pointer to the vtable not the full table. The vtables themselves are generally stored in a read only data segment. 

Yep, already edited my post. I should remember to hold off on posting in the morning until after breakfast. laugh.png

 

 

all this seems to imply that all method calls require dereferencing the object's vmt pointer to get the jump address of the actual method to use. i thought non-virtual methods had their jump addresses stored in the objects, to avoid the derferencing overhead.

 

so all method calls become: gosub vmt_ptr->method->address ?  

 

IE gosub to the address of <method> stored in the vmt pointed to by vmt_ptr ?

 

for non-virtual, I thought it was more like: gosub instance.method_address[method]

 

and a stand alone function, by comparison is simply: gosub method_address, correct?  

Share this post


Link to post
Share on other sites


for non-virtual, I thought it was more like: gosub instance.method_address[method]
 
and a stand alone function, by comparison is simply: gosub method_address, correct?  

 

A non-virtual member function and a stand-alone function are pretty much the same thing. The method address is known after linking. The only real difference is the implicit "this" argument.

Share this post


Link to post
Share on other sites
fascinating! i thought local declarations did a new in the background and a dispose on return, using the heap.[/quote] Things only go on the heap if they are explicitly allocated, either directly or indirectly. If the object you create on the stack uses allocation with new, then that data would go on the heap. The pointer to that data would be on the stack. If the object you created was on the heap then both the pointer and the other data would both go on the heap. Its basically recursive. Say you created an object on the heap and called a member function. If that member function has local variables then they would be pushed onto the stack even though the object itself was allocated on the heap. This is due to the way the compiler treats executable code.

would object1 go on the stack when main was called? or is main a special case and it goes in the data segment?
main is exactly like any other function except it is called by the operating system rather than your program.

object1 (its member var storage space) would go in the data segment?
That would go in the data segment. While I said that main is a special case, it's slightly more complicated than I implied. main is actually called indirectly by the operating system in most environments. The operating system will generally call a function implemented in the run-time library. You can read a little about it here. That link mentions that the run-time library will initialize global variables before main is called. If the operating system called main directly then none of the constructors for the global variables would be called, among other things.

Share this post


Link to post
Share on other sites


A non-virtual member function and a stand-alone function are pretty much the same thing. The method address is known after linking.

 

ok, so the vmt addresses replace the non-virtual method calls at link time, correct?

 

i _thought_ they were the same thing....

Share this post


Link to post
Share on other sites

Things only go on the heap if they are explicitly allocated, either directly or indirectly.

 

i was under the impression that all were allocated on the heap, except global instances, which were allocated in the data segment at load time.

 

thanks for clarifying that. (lending a machete to my intellectual thicket).

 

and locally declared instances, being allocated on the heap, are automatically de-allocated when they go out of scope. and they don't use the heap so no possibility of memory leak. just the usual cross your i's and dot your t's stuff with malloc and free under different names (new and dispose) - correct?

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

 

would object1 go on the stack when main was called? or is main a special case and it goes in the data segment?
main is exactly like any other function except it is called by the operating system rather than your program.

Almost. You cannot legally call main from your program in c++. This design was because the compiler may wish to insert additional setup code at the beginning of main.

Share this post


Link to post
Share on other sites
class MyClass; //Declare a class. Nothing created yet.

int main()
{
    MyClass *instance = new MyClass; //An instance created on the heap.

    MyClass instance; //An instance created on the stack.
}

If declared on the stack, the class is constructed when the code calls that function and the function reaches that line of code. Whether or not they are allocated before the function is called I *think* might be up to the compiler.

 

The above has to do with variables created inside functions.

 

If a variable is global, or if a variable is a static member-variable of a class (which is just a global variable in the class's namespace), then you can't count on the order or timing of when they are constructed. The only thing you can depend on is that they'll be constructed before int main() enters... but other than that, the order is compiler-specific.

 

This is one of the reasons why programmers avoid global variables in C++. I've dealt with, in my own code, far too many bugs caused by global variables.

 

Global variables are a convenience, but they end up hurting more than they help. Global constants are usually fine though.

Share this post


Link to post
Share on other sites


If a variable is global, or if a variable is a static member-variable of a class (which is just a global variable in the class's namespace), then you can't count on the order or timing of when they are constructed. The only thing you can depend on is that they'll be constructed before int main() enters... but other than that, the order is compiler-specific.

 

right, startup code constructor hell. you can't rely on other globals being valid during your custom constructor. i hear about this a fair amount. i would think an explicit init() would take care of it. bad form?

Share this post


Link to post
Share on other sites

If a variable is global, or if a variable is a static member-variable of a class (which is just a global variable in the class's namespace), then you can't count on the order or timing of when they are constructed. The only thing you can depend on is that they'll be constructed before int main() enters... but other than that, the order is compiler-specific.

 
right, startup code constructor hell. you can't rely on other globals being valid during your custom constructor. i hear about this a fair amount. i would think an explicit init() would take care of it. bad form?
Very bad.

I once had the misfortune of working on a port that severely abused global variables.

It took so long to run the pre-main initialization that we could not pass certification as-is. We had to hack in to the runtime and display the splash screen. Rather than the normal sleep while a splash was running, it spent those seconds initializing variables and doing other pre-main work.

IIRC it took something like five seconds between execution start and the beginning of main.

Just don't start down that road. It is a horrible thing.

Share this post


Link to post
Share on other sites

except global instances, which were allocated in the data segment at load time.

 

It depends on the compiler you use, but generally yes, a global object will exist in the data segment, specifically in the bss section if it is a basic datatype or a structure containing basic datatypes. This isn't written in stone, though, and it's possible for it to also be allocated on the heap, or even on the stack.

Edited by TheComet

Share this post


Link to post
Share on other sites


It took so long to run the pre-main initialization that we could not pass certification as-is. We had to hack in to the runtime and display the splash screen. Rather than the normal sleep while a splash was running, it spent those seconds initializing variables and doing other pre-main work.

 

OMG! LOL!

 

so it had a ton of global instances with custom constructor code that took many seconds before you got control in main() ? <g>

 

i had to "hack in" a loading splash screen for caveman too. the game does a one time load of all assets at program start, about 10-20 seconds - after that, no load screens whatsoever. when i switched from d3dx fonts to a custom font using d3dx sprites for speed, i found i no longer had a font available for loading progress messages. i had to temporarily load in a splash screen. the only non sequential vidram allocation in the whole game. and its the first one!  i really should fix that. make it texture #0, the first texture loaded, and move texture #0 (grass tile #1) to the end of the list. but then i have a 1024x1024 sitting at the base of graphics ram the whole game for no reason. but right now, it loads the splash screen, then it loads everything else, then frees the splash screen. so i have nice contiguous ram, with a 1024x1024 hole at the start! <g>.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

If a variable is global, or if a variable is a static member-variable of a class (which is just a global variable in the class's namespace), then you can't count on the order or timing of when they are constructed. The only thing you can depend on is that they'll be constructed before int main() enters... but other than that, the order is compiler-specific.

You can't count on the initialization of globals defined in different translation units. Globals defined in the same translation unit are well defined to initialize in the order they are found. Not that that helps much.

Share this post


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

  • Advertisement