virtual 关键字如何影响内存位置?

问题描述

我之前有一个工作面试,被问到以下代码输出是什么:

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

我的问题是:看似无关紧要的虚函数调用如何影响结果对象的内存布局从而导致这种情况?

解决方法

向类添加 1 个或多个 purrr::map_df(dat_list,~as.data.frame(t(.x))) 方法会导致(在本例中为 1)该类的对象实例包含指向编译器管理的“虚拟方法表”的隐藏指针" (vtable) 在对象内存的前面,例如:

virtual

假设 int *data = &a; A a; data -> -------------------- | vtable | -> [0]: &A::f | (8 bytes) | |------------------| data[2] -> | data[0]: 22 | |------------------| | data[1]: 33 | -------------------- 为 4(通常是这种情况),能够通过指向对象的 sizeof(int) 指针访问对象的 data[0] 成员,该指针正在索引第 3 个int*,告诉我们在对象的前面有一个额外的 8 个字节,如果代码被编译为 64 位(如果它被编译为 32 位, vtable 指针将是 4 个字节,int 将访问 data[2])。

没有任何 A::data[1] = 33 方法,不存在 vtable,因此不存在额外的 8 个字节,因此从对象前面开始索引到第 3 个 virtual 将超过将对象放入周围的内存中,例如:

int

1:这是编译器的实现细节。 C++ 标准没有规定如何实现虚拟方法。不过,大多数编译器会使用 vtable。

,

virtual 关键字如何影响内存位置?

因为该类没有任何其他虚函数,所以添加一个virtual函数会添加一个指向该类的指针。指针指向一个 vtable,该类是唯一的。

指针可能是 int 的大小,但也可能不是。在 64 位系统上,它通常是 int 大小的两倍。但不一定是这样。

指针可能位于类内存布局的开头,但也可能不在。开头是一个常见的位置,但流行的商业 C++ 编译器有时会将它放在其他地方。

我之前有一个工作面试,被问到下面代码的输出是什么

这是未定义的行为——这意味着对程序可能会做什么做出任何声明都是危险的。

具体来说,这段代码:

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

声称指向A的指针是一个指向int的指针。这不是真的。 A 不是 int

由于它是未定义行为,因此您无法保证一次运行中观察到的行为与任何其他运行中观察到的行为相匹配。因此,应避免使用 UB。