Reputation: 479
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
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
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