Vtables 和这个指针
vtables and this pointer
我试图更多地了解vtables和vpointers的内部工作原理,所以我决定尝试使用一些技巧直接访问vtable。我创建了两个类,Base
和 Derv
,每个类有两个virtual
函数(Derv
覆盖Base
的函数)。
class Base
{
int x;
int y;
public:
Base(int x_, int y_) : x(x_), y(y_) {}
virtual void foo() { cout << "Base::foo(): x = " << x << 'n'; }
virtual void bar() { cout << "Base::bar(): y = " << y << 'n'; }
};
class Derv: public Base
{
int x;
int y;
public:
Derv(int x_, int y_) : Base(x_, y_), x(x_), y(y_) {}
virtual void foo() { cout << "Derived::foo(): x = " << x << 'n'; }
virtual void bar() { cout << "Derived::bar(): y = " << y << 'n'; }
};
现在,编译器为每个类添加一个 vtable 指针,占用内存中的前 4 个字节(32 位)。我通过将对象的地址转换为size_t*
来访问此指针,因为指针指向另一个大小为 sizeof(size_t)
的指针。现在可以通过索引 vpointer 并将结果强制转换为适当类型的函数指针来访问虚函数。我将这些步骤封装在一个函数中:
template <typename T>
void call(T *ptr, size_t num)
{
typedef void (*FunPtr)();
size_t *vptr = *reinterpret_cast<size_t**>(ptr);
FunPtr fun = reinterpret_cast<FunPtr>(vptr[num]);
//setThisPtr(ptr); added later, see below!
fun();
}
当以这种方式调用其中一个成员函数时,例如 call(new Base(1, 2), 0)
调用 Base::foo(),很难预测会发生什么,因为它们是在没有 this
-指针的情况下调用的。我通过添加一点模板化函数来解决这个问题,知道 g++ 将 this
指针存储在ecx
寄存器中(但这迫使我使用 -m32
编译器标志进行编译):
template <typename T>
void setThisPtr(T *ptr)
{
asm ( mov %0, %%ecx;" :: "r" (ptr) );
}
取消上面代码片段中的setThisPtr(ptr)
行注释现在使其成为一个工作程序:
int main()
{
Base* base = new Base(1, 2);
Base* derv = new Derv(3, 4);
call(base, 0); // "Base::foo(): x = 1"
call(base, 1); // "Base::bar(): y = 2"
call(derv, 0); // "Derv::foo(): x = 3"
call(derv, 1); // "Derv::bar(): y = 4"
}
我决定分享这个,因为在编写这个小程序的过程中,我对 vtables 的工作原理有了更多的了解,它可能会帮助其他人更好地理解这些材料。但是我仍然有一些问题:
1. 编译 64 位二进制文件时,使用哪个寄存器 (gcc 4.x) 来存储 this 指针?我尝试了此处记录的所有 64 位寄存器:http://developers.sun.com/solaris/articles/asmregs.html
2. 何时/如何设置此指针?我怀疑编译器通过对象在每个函数调用上设置 this 指针的方式与我刚刚的方式类似。这是多态性的实际工作方式吗?(通过先设置 this 指针,然后从 vtable 调用虚函数?
在 Linux x86_64 上,我相信其他类 UNIX 操作系统,函数调用遵循 System V ABI(AMD64),它本身遵循 IA-64 C++ ABI 表示C++。 根据方法的类型,this
指针通过第一个参数或第二个参数隐式传递(当返回值具有非平凡的复制构造函数或析构函数时,它必须作为堆栈上的临时存在,并且第一个参数隐式是指向该空间的指针);否则,虚方法调用与 C 中的函数调用相同(整数/指针参数在 %rdi
、%rsi
、%rdx
、%rcx
、%r8
、%r9
、溢出到堆栈;整数/指针返回在 %rax
中;浮点数在 %xmm0
- %xmm7
等)。 虚拟方法调度的工作原理是在 vtable 中查找指针,然后像非虚拟方法一样调用它。
我不太熟悉 Windows x64 约定,但我相信它是相似的,因为C++方法调用遵循与 C 函数调用完全相同的结构(使用 与 Linux 不同的寄存器),只是首先使用隐式this
参数。
- 1d 智能指针不适用于语法 (*)++
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 为什么使用 "this" 指针调用派生成员函数?
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用指针从C++中的数组中获取最大值
- 助记符和指向成员语法的指针
- 嵌入方指针压缩已禁用
- 数组的指针从不分段故障
- C++ 指针的内存地址和指向数组的内存地址如何相同?
- 何时在引用或唯一指针上使用移动语义
- QMetaObject invokeMethod的基于函数指针的语法
- 如何从 std::atomic 中提取指针 T<T>?
- 如何在 C# 中映射双 C 结构指针?
- C++将浮点指针值舍入为小数位数
- 为什么++(*p)更改指针值
- 调整大小后指向元素值的指针unordered_map有效?
- 正在将指针转换为范围
- 使用指向成员的指针将成员函数作为参数传递
- Vtables是否仅与指向基类的指针一起使用
- Vtables 和这个指针