7

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
  • 4
    I recommend 'Inside the C++ Object Model' by Stanley Lippman if you want to how C++ objects can be modeled (I say can because there are multiple ways to implement C++ internals). Commented Mar 11, 2010 at 6:59
  • If you correct your code, why not run your compiler with assembler output and see what it generates? Commented Jul 4, 2010 at 12:23

4 Answers 4

13

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
3

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.

8 Comments

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?
@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.
@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.
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.
@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.
|
1

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"

Comments

0
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.