Aroic
Aroic

Reputation: 479

How does the virtual keyword affect memory locations?

I had a job interview earlier and was asked what the output of the following code is:

struct A {
    int data[2];
    A(int x, int y) { data[0] = x; data[1] = y; }

    virtual void f() {}
};

int main(){
    A a(22, 33);
    int* data = (int*)&a;
    cout << data[2] << endl;
}

I talked through it but couldn't figure it out exactly. He mentioned that the virtual function was a hint. Afterwards I compiled it and got the output:

22

I then thought about the virtual function and removed it:

struct A {
    int data[2];
    A(int x, int y) { data[0] = x; data[1] = y; }

    //virtual void f() {}
};

int main(){
    A a(22, 33);
    int* data = (int*)&a;
    cout << data[2] << endl;
}

Resulting in the ouput:

0

My question is: How does the seemingly inconsequential virtual function call affect the resulting object's memory layout to cause this?

Upvotes: 0

Views: 458

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 596256

Adding 1 or more virtual methods to a class causes (in this case 1) an object instance of that class to contain a hidden pointer to a compiler-managed "virtual method table" (vtable) at the front of the object's memory, eg:

int *data = &a;     A a;
   data -> --------------------
           | vtable           | -> [0]: &A::f
           | (8 bytes)        |
           |------------------|
data[2] -> | data[0]: 22      |
           |------------------|
           | data[1]: 33      |
           --------------------

Assuming sizeof(int) is 4 (which is usually the case), being able to access the object's data[0] member via an int* pointer to the object, where that pointer is indexing the 3rd int, tells us that there is an extra 8 bytes present at the front of the object, which can be accounted for by the vtable pointer, if the code is being compiling as 64bit (if it were compiled as 32bit instead, the vtable pointer would be 4 bytes, and data[2] would be accessing A::data[1] = 33).

Without any virtual methods, there is no vtable present, so the extra 8 bytes are not present, and thus indexing to the 3rd int from the front of the object will exceed past the bounds of the object into surrounding memory, eg:

int *data = &a;     A a;
   data -> --------------------
           | data[0]: 22      |
           |------------------|
           | data[1]: 33      |
           --------------------
data[2] -> 

1: this is an implementation detail of the compiler. The C++ standard doesn't dictate how virtual methods are to be implemented. Most compilers will use a vtable, though.

Upvotes: 2

Drew Dormann
Drew Dormann

Reputation: 63775

How does the virtual keyword affect memory locations?

Because the class doesn't have any other virtual functions, adding a virtual function adds a pointer to the class. The pointer points to a vtable, unique to that class.

The pointer might be the size of an int, but it might not. On a 64 bit system, it is typically twice the size of an int. But it doesn't have to be.

The pointer might be at the beginning of the class's memory layout, but it might not. The beginning is a common location, but popular commercial C++ compilers will sometimes place it elsewhere.

I had a job interview earlier and was asked what the output of the following code is

It is Undefined Behavior -- meaning that it would be dangerous to make any claim as to what the program might do.

Specifically, this code:

int* data = (int*)&a;
cout << data[2] << endl;

claims that a pointer to A is a pointer to int. This is not true. An A is not an int.

Since it is Undefined Behavior, you are given no assurances that the observed behavior from one run will match the observed behavior from any other run. Because of this, UB is to be avoided.

Upvotes: 2

Related Questions