正在读取虚拟函数表 (vtable) 指针?

Reading the virtual function table (vtable) pointer?

本文关键字:vtable 指针 读取 虚拟 函数      更新时间:2023-10-16

有没有一种定义明确的方法来访问类的vtable?在Visual Studio中调试时,我可以扩展"this",例如:this->_ptr->__vfptr。但是此路径似乎无法从代码中获得。

我需要它来对自定义堆实现(嵌入式环境)进行单元测试。

背景

我们有一个错误,在我们的自定义堆上分配的对象(只不过是一个特定大小的数组)按预期工作,直到我们想添加一个具有虚拟函数的对象(我们花了相当长的时间才意识到这种添加是问题的原因)。我们犯的错误是将一个对象分配给内存,在分配之前没有初始化任何对象。我们在编写该代码时没有太注意,并且由于它与其他所有内容一起使用并经过测试,我们认为它可以工作。下面是一些示例代码:

int array_ptr[sizeof(SomeObject)];
*((SomeObject*) array_ptr) = SomeObject(); // Does only partially initialize the object!

一旦我们意识到这条线是问题所在,也很清楚为什么会这样。

啊哈,我现在明白了,评论中的澄清。

您正在仅具有CFoo大小的原始内存上调用CFoo::operator=。这确实不会在常见的实现上设置一个vtable。这特定于C++中的分配的工作方式。C++中的对象分配定义为切片。如果将Derived对象分配给Base类,则会调用Base::operator=(Base const& src).这仅复制Derived对象的 Base 子对象。

C++选择此模型的原因是,这意味着当您为Base对象分配Derived值时,它的大小不会改变,代价是丢失额外的信息。

净效果是C++对象在构造后不会改变类型。实际上,这意味着类型和vtable可以由构造函数修复。赋值运算符不会触摸它。

因此,通过在原始内存上调用赋值运算符,您将获得未定义的行为,特别是未初始化的(垃圾)vtable。你不能指望它都是零。此外,在具有多重和虚拟继承的更复杂的情况下,还有其他数据字段来查找各种子对象。这些也将是未初始化的。请注意,这些附加数据字段可能包含绝对指针。memcpy这样的对象,您将指向原始对象的子对象。

你能察觉到这一点吗?不。您访问内存的所有尝试都是未定义的行为,因为原始内存中没有CFoo对象。

解决方案是放置new。这是将原始记忆变成物体的神奇咒语。它可以使用任何构造函数,包括移动构造函数和复制构造函数,但(除非例外)将为你留下一个有效的对象,具有适当的多态行为。

好的,所以从上面的 MSalters 和其他评论者那里学习,我知道没有直接的方法来阅读 vtable 指针。但是,我想出了一个足以满足我需求的解决方案(即测试 vtable 指针是否正确初始化)。所以这是代码(请注意,我假设我得到的是 vtable 指针 sizeof(size_t) == sizeof(EmptyClassWithOneVirtualFunction)):

class EmptyClassWithOneVirtualFunction
{
virtual void testFunction() {}
};
void test_staticNew_object_vtable()
{
EmptyClassWithOneVirtualFunction correctObject;
EmptyClassWithOneVirtualFunction* object = mem::static_new<EmptyClassWithOneVirtualFunction>();
size_t* correctObjectVtablePtr  = ( (size_t*) &correctObject );
size_t* objectVtablePtr         = ( (size_t*) object );
TS_ASSERT_EQUALS( *objectVtablePtr, *correctObjectVtablePtr );
}

应该指出的是,这是在调试模式下构建的测试代码,没有优化。对我来说,即使以这种不完全"安全"的方式也能捕获此错误,比仅仅因为没有正确的方法而跳过它更有价值。