为什么 vptr 存储为具有虚函数的 class 内存中的第一个条目?
Why is vptr stored as the first entry in the memory of a class with virtual functions?
对于某些编译器,如果 class 具有虚函数,则可以使用其对象的第一个字节的地址访问其 vptr。例如,
class Base{
public:
virtual void f(){cout<<"f()"<<endl;};
virtual void g(){cout<<"g()"<<endl;};
virtual void h(){cout<<"h()"<<endl;};
};
int main()
{
Base b;
cout<<"Address of vtbl:"<<(int *)(&b)<<endl;
return 0;
}
我知道它依赖于不同的编译器行为。由于存在将 vptr 存储为第一个条目的情况,这样做的好处是什么?这有助于提高性能还是仅仅因为使用 &b 更容易访问 vbtl?
这是一个实现细节,但实际上很多实现都是这样做的。
相当高效和方便。假设您需要为给定对象调用虚函数。您有一个指向该对象的指针和虚函数索引。您需要以某种方式找到应该使用该索引和该对象调用哪个函数。好的,您只需访问指针后面的第一个 sizeof(void*)
字节并找到 vtable 所在的位置,然后访问 vtable 的必要元素以获取函数地址。
您可以存储 "vtable for each object" 的单独映射或其他内容,但如果您决定要将 vptr 存储在对象内,那么使用第一个字节而不是最后一个字节或任何其他字节是合乎逻辑的放置,因为使用这种方法,一旦有了指向对象的指针,您就知道在哪里可以找到 vptr,不需要额外的数据。
虽然这是实现定义的,但似乎没有太多真正的选择。
首先我们可以看到你的以太币必须有一个 vptr
或一个嵌入的 vtable
。后者意味着您将不得不在构造时复制 vtable
并且它会消耗更多内存,但具有避免在每次方法调用时取消引用一个指针的优点。根据具体情况,它们可能都有很好的论据 - 大多数实现都选择减少构建时间和整体内存消耗,而不是节省调度时间。
When chosen vptr
approach we see that we must keep binary compatibility of the layout of base and derived classes.首先,我们可以通过(经常)使用一个 vptr
来实现这一点,出于兼容性原因,这个 vptr
必须存在于最基本的 class.
中
在处理简单继承时,在派生与基础之间进行转换的最直接方法 class 是保留指针值,这意味着布局必须首先是基础字段 class 后跟派生的 classes 对它有贡献。
现在我们已经很接近将 vptr
放在首位的原因了。它必须位于对象的开头附近,因为它必须位于对象的最基本部分内。
那么我们将它放在偏移量 0 上的原因可能是它是一个适用于所有 classes 的一致偏移量。您根本无法保证 vptr
.
之前可以放置任何数据
将 vptr
放在偏移量 0 处也有一些优点。如果您知道该对象有一个 vptr
,您就知道您必须查看偏移量 0 而无需知道该对象的类型(不仅仅是它有一个 vptr
)。这对于某些调试目的来说很方便(vtable
通常包含足够的信息来推断实际类型)。特别是这使得 typeid
和类似的实现更简单,因为您只需查看相同的偏移量即可通过预定义的偏移量检索 type_info
节点 - 这意味着您可以共享 [=22] 的实际代码=].
对于某些编译器,如果 class 具有虚函数,则可以使用其对象的第一个字节的地址访问其 vptr。例如,
class Base{
public:
virtual void f(){cout<<"f()"<<endl;};
virtual void g(){cout<<"g()"<<endl;};
virtual void h(){cout<<"h()"<<endl;};
};
int main()
{
Base b;
cout<<"Address of vtbl:"<<(int *)(&b)<<endl;
return 0;
}
我知道它依赖于不同的编译器行为。由于存在将 vptr 存储为第一个条目的情况,这样做的好处是什么?这有助于提高性能还是仅仅因为使用 &b 更容易访问 vbtl?
这是一个实现细节,但实际上很多实现都是这样做的。
相当高效和方便。假设您需要为给定对象调用虚函数。您有一个指向该对象的指针和虚函数索引。您需要以某种方式找到应该使用该索引和该对象调用哪个函数。好的,您只需访问指针后面的第一个 sizeof(void*)
字节并找到 vtable 所在的位置,然后访问 vtable 的必要元素以获取函数地址。
您可以存储 "vtable for each object" 的单独映射或其他内容,但如果您决定要将 vptr 存储在对象内,那么使用第一个字节而不是最后一个字节或任何其他字节是合乎逻辑的放置,因为使用这种方法,一旦有了指向对象的指针,您就知道在哪里可以找到 vptr,不需要额外的数据。
虽然这是实现定义的,但似乎没有太多真正的选择。
首先我们可以看到你的以太币必须有一个 vptr
或一个嵌入的 vtable
。后者意味着您将不得不在构造时复制 vtable
并且它会消耗更多内存,但具有避免在每次方法调用时取消引用一个指针的优点。根据具体情况,它们可能都有很好的论据 - 大多数实现都选择减少构建时间和整体内存消耗,而不是节省调度时间。
When chosen vptr
approach we see that we must keep binary compatibility of the layout of base and derived classes.首先,我们可以通过(经常)使用一个 vptr
来实现这一点,出于兼容性原因,这个 vptr
必须存在于最基本的 class.
在处理简单继承时,在派生与基础之间进行转换的最直接方法 class 是保留指针值,这意味着布局必须首先是基础字段 class 后跟派生的 classes 对它有贡献。
现在我们已经很接近将 vptr
放在首位的原因了。它必须位于对象的开头附近,因为它必须位于对象的最基本部分内。
那么我们将它放在偏移量 0 上的原因可能是它是一个适用于所有 classes 的一致偏移量。您根本无法保证 vptr
.
将 vptr
放在偏移量 0 处也有一些优点。如果您知道该对象有一个 vptr
,您就知道您必须查看偏移量 0 而无需知道该对象的类型(不仅仅是它有一个 vptr
)。这对于某些调试目的来说很方便(vtable
通常包含足够的信息来推断实际类型)。特别是这使得 typeid
和类似的实现更简单,因为您只需查看相同的偏移量即可通过预定义的偏移量检索 type_info
节点 - 这意味着您可以共享 [=22] 的实际代码=].