virtual 关键字如何影响内存位置?
How does the virtual keyword affect memory locations?
我早些时候参加了一次工作面试,被问到以下代码的输出是什么:
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;
}
我讲了一遍,但没弄清楚。他提到虚函数是一个提示。之后我编译它并得到输出:
22
后来想到虚函数,去掉了:
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;
}
导致输出:
0
我的问题是:看似无关紧要的虚函数调用是如何影响结果对象的内存布局从而导致这种情况的?
向 class 添加 1 个或多个 virtual
方法会导致(在本例中为 1)该 class 的对象实例在对象内存的前面包含一个指向编译器管理的“虚方法table”(vtable)的隐藏指针,例如:
int *data = &a; A a;
data -> --------------------
| vtable | -> [0]: &A::f
| (8 bytes) |
|------------------|
data[2] -> | data[0]: 22 |
|------------------|
| data[1]: 33 |
--------------------
假设 sizeof(int)
是 4(通常是这种情况),能够通过指向对象的 int*
指针访问对象的 data[0]
成员,该指针正在索引第 3 个 int
,告诉我们在对象的前面有一个额外的 8 个字节,如果代码被编译为 64 位(如果它被编译为 32 位,vtable 指针将是 4 个字节,并且 data[2]
将访问 A::data[1] = 33
).
没有任何 virtual
方法,没有 vtable 存在,所以额外的 8 个字节不存在,因此索引到第 3 个 int
对象会越过对象的边界进入周围的内存中,例如:
int *data = &a; A a;
data -> --------------------
| data[0]: 22 |
|------------------|
| data[1]: 33 |
--------------------
data[2] ->
1:这是编译器的实现细节。 C++ 标准没有规定如何实现虚拟方法。不过,大多数编译器会使用 vtable。
How does the virtual keyword affect memory locations?
因为 class 没有任何其他虚函数,所以添加 virtual
函数会添加指向 class 的指针。指针指向一个vtable,唯一的那个class.
指针可能是 int
的大小,但也可能不是。在 64 位系统上,它通常是 int 大小的两倍。但它不一定是。
指针可能位于 class 内存布局的开头,但也可能不是。开头是一个常见的位置,但流行的商业 C++ 编译器有时会将其放在其他位置。
I had a job interview earlier and was asked what the output of the following code is
这是未定义的行为——这意味着对程序可能会做什么做出任何声明都是危险的。
具体来说,这段代码:
int* data = (int*)&a;
cout << data[2] << endl;
声称指向 A
的 指针是指向 int
的 指针。这不是真的。 A
不是 int
.
由于它是未定义的行为,因此无法保证从一个 运行 观察到的行为将与从任何其他 运行 观察到的行为相匹配。因此,要避免UB。
我早些时候参加了一次工作面试,被问到以下代码的输出是什么:
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;
}
我讲了一遍,但没弄清楚。他提到虚函数是一个提示。之后我编译它并得到输出:
22
后来想到虚函数,去掉了:
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;
}
导致输出:
0
我的问题是:看似无关紧要的虚函数调用是如何影响结果对象的内存布局从而导致这种情况的?
向 class 添加 1 个或多个 virtual
方法会导致(在本例中为 1)该 class 的对象实例在对象内存的前面包含一个指向编译器管理的“虚方法table”(vtable)的隐藏指针,例如:
int *data = &a; A a;
data -> --------------------
| vtable | -> [0]: &A::f
| (8 bytes) |
|------------------|
data[2] -> | data[0]: 22 |
|------------------|
| data[1]: 33 |
--------------------
假设 sizeof(int)
是 4(通常是这种情况),能够通过指向对象的 int*
指针访问对象的 data[0]
成员,该指针正在索引第 3 个 int
,告诉我们在对象的前面有一个额外的 8 个字节,如果代码被编译为 64 位(如果它被编译为 32 位,vtable 指针将是 4 个字节,并且 data[2]
将访问 A::data[1] = 33
).
没有任何 virtual
方法,没有 vtable 存在,所以额外的 8 个字节不存在,因此索引到第 3 个 int
对象会越过对象的边界进入周围的内存中,例如:
int *data = &a; A a;
data -> --------------------
| data[0]: 22 |
|------------------|
| data[1]: 33 |
--------------------
data[2] ->
1:这是编译器的实现细节。 C++ 标准没有规定如何实现虚拟方法。不过,大多数编译器会使用 vtable。
How does the virtual keyword affect memory locations?
因为 class 没有任何其他虚函数,所以添加 virtual
函数会添加指向 class 的指针。指针指向一个vtable,唯一的那个class.
指针可能是 int
的大小,但也可能不是。在 64 位系统上,它通常是 int 大小的两倍。但它不一定是。
指针可能位于 class 内存布局的开头,但也可能不是。开头是一个常见的位置,但流行的商业 C++ 编译器有时会将其放在其他位置。
I had a job interview earlier and was asked what the output of the following code is
这是未定义的行为——这意味着对程序可能会做什么做出任何声明都是危险的。
具体来说,这段代码:
int* data = (int*)&a;
cout << data[2] << endl;
声称指向 A
的 指针是指向 int
的 指针。这不是真的。 A
不是 int
.
由于它是未定义的行为,因此无法保证从一个 运行 观察到的行为将与从任何其他 运行 观察到的行为相匹配。因此,要避免UB。