tags:

views:

287

answers:

4

When we create an object of a class what does it memory map look like. I am more interested in how the object calls the non virtual member functions. Does the compiler create a table like vtable which is shared between all objects?

class A
{
public:
  void f0() {}
  int int_in_b1;
};

A * a = new A;

What will be the memory map of a?

+2  A: 

Recognize that the C++ language doesn't specify or mandate everything about memory layout for objects. That said, most compilers do it pretty much the same.

In your example, objects of type A require only enough memory to hold an int. Since it has no virtual functions, it needs no vtable. If the f0 member had been declared virtual, then objects of type A would typically start with a pointer to the class A vtable (shared by all objects of type A) followed by the int member.

In turn, the vtable has a pointer to each virtual function, defined, inherited or overridden. Calling a virtual function for an object consists of following the pointer to the vtable from the object, then using a fixed offset into the vtable (determined at compile time for each virtual function) to find the address of the function to call.

MtnViewMark
I know how a vtable works. I am interested in how compiler deals with non-virtual function. Is there a separate table for them too?
Bruce
@Peter: Functions have no bearing on the size of the class, and no bearing on layout. The functions are just like any other function you write, they reside in memory somewhere waiting to be called. The only thing about member functions is they have an implicit `this` pointer you don't see.
GMan
So when I write a.f0() how does the compiler get the address of f0()?
Bruce
@Peter: The same way as any other function. Skipping all the technical addressing business, the function has some compiled address. It's this address the compiler uses to invoke a function on a class instance.
GMan
For the compiler, finding the address of the function to call in a.f0() is no different than calling a global function: It is just a global function name. The compiler generates a direct call instruction, and the linker fixes it up. The trick, is that for non-virtual functions, the compiler "mangles" the names so two non-virtual functions with the same name in two different classes, end up with two different global names in the complete program. For example, Class A's f0() might be called __1A2f0vv globally.
MtnViewMark
k...but if the function is declared private how does it ensure 'a' cannot call it. Also we can't call f0 without creating an object of A. How is all this done?
Bruce
@Peter: Accessibility (public, protected, private) is checked at compile-time. The compiler will give an error and not emit code (e.g. assembly) if you violate it. Similarly for checking that non-static member functions are called with an instance of the class: try something invalid and you get an error.
Roger Pate
@Peter: To be even more clear, once compiled, there is no enforcement of accessibility. Writing in assembly, for example, one can call private or protected members, and call non-static methods with non-instances.
MtnViewMark
A: 
class A
{
public:
  void f0() {}
  void f1(int x) {int_in_b1 = x; }
  int int_in_b1;
};

A *a = new A();

is internally implemented (represented) like this: (function name are actually mangled)

struct A
{
  int int_in_b1;
};

void Class_A__constructor(struct a*) {} // default constructor
void Class_A__f0(struct a*) {}
void Class_A__f1(struct a*, int x) {a->int_in_b1 = x;}

// new is translated like this: (inline)
void* new() {
  void* addr = malloc(sizeof(struc a));
  Class_A__constructor(addr);
  return addr;
}

It can be verify by doing a command "nm" on the object file (result with mangled named)

Phong
You copied the `A a = new A();` error from the question.
Roger Pate
@Roger: Thank you, I didn't notice
Phong
+1  A: 

functions are not stored based on what class they are in.

usually the compiler will just treat any member function just like any other function except adds an argument for the 'this' pointer. which is automatically passed to the function when you called it based on the address of the object it is called on.

all functions, static, member, or even virtual member are stored in memory in the same way, they are all just functions.

when the compiler builds the code it pretty much hard codes where it goes into memory, then the linker goes through your code and replaces the "call the function with this name" command with "call the function at this hard coded address"

matt
+5  A: 

You can imagine this code:

struct A {
  void f() {}
  int int_in_b1;
};

int main() {
  A a;
  a.f();
  return 0;
}

Being transformed into something like:

struct A {
  int int_in_b1;
};
void A__f(A* const this) {}

int main() {
  A a;
  A__f(&a);
  return 0;
}

Calling f is straight-forward because it's non-virtual. (And sometimes for virtual calls, the virtual dispatch can be avoided if the dynamic type of the object is known, as it is here.)


A longer example that will either give you an idea about how virtual functions work or terribly confuse you:

struct B {
  virtual void foo() { puts(__func__); }
};
struct D : B {
  virtual void foo() { puts(__func__); }
};

int main() {
  B* a[] = { new B(), new D() };
  a[0]->foo();
  a[1]->foo();
  return 0;
}

Becomes something like:

void B_foo(void) { puts(__func__); }
void D_foo(void) { puts(__func__); }

struct B_VT {
  void (*foo)(void);
}
B_vtable = { B_foo },
D_vtable = { D_foo };

typedef struct B {
  struct B_VT* vt;
} B;
B* new_B(void) {
  B* p = malloc(sizeof(B));
  p->vt = &B_vtable;
  return p;
}

typedef struct D {
  struct B_VT* vt;
} D;
D* new_D(void) {
  D* p = malloc(sizeof(D));
  p->vt = &D_vtable;
  return p;
}

int main() {
  B* a[] = {new_B(), new_D()};
  a[0]->vt->foo();
  a[1]->vt->foo();
  return 0;
}

Each object only has one vtable pointer, and you can add many virtual methods to the class without affecting object size. (The vtable grows, but this is stored once per class and is not significant size overhead.) Note that I've simplified many details in this example, but it does work: destructors are not addressed (which should additionally be virtual here), it leaks memory, and the __func__ values will be slightly different (they're generated by the compiler for the current function's name), among others.

Roger Pate
The second example is a few weeks old that I had written, and I see now that I forgot to add *this* pointers, even though they aren't used. If you don't see how to add them, just let me know and I can edit; otherwise I'll keep it the same as the compiled code in the codepad link.
Roger Pate