获取虚函数的地址
虚函数是C++中用于实现多态的一种机制,该机制的原理在此不做赘述。本文主要讨论如何获取虚表以及虚函数的地址?
class ClassA {
private:
int _a;
double _b;
public:
ClassA(int a, double b) : _a(a), _b(b) { }
virtual int funcA(int a) { return a; }
virtual double funcB() { return _b; }
};
#include <iostream>
#define PRINT(val) \
std::cout << #val << ": " << val << std::endl
int main()
{
ClassA classA(3, 1.2);
intptr_t* vptr = reinterpret_cast<intptr_t*>(&classA); // 虚表指针
intptr_t vtable_addr = *vptr; // 解引用,获取虚表的地址
intptr_t* virtual_func_ptr = reinterpret_cast<intptr_t*>(vtable_addr); // 指向第一个虚函数地址
intptr_t funcA_addr = *virtual_func_ptr; // 解引用,获取funcA的地址
intptr_t funcB_addr = *(virtual_func_ptr + 1); // 解引用,获取funcB的地址
intptr_t funcB_addr2 = *reinterpret_cast<intptr_t*>(vtable_addr + sizeof(intptr_t)); // funcB的地址
std::cout << std::hex;
PRINT(vptr);
PRINT(vtable_addr);
PRINT(funcA_addr);
PRINT(funcB_addr);
PRINT(funcB_addr2);
typedef int (*FuncAType)(ClassA*, int);
typedef double (*FuncBType)(ClassA*);
FuncAType funcA = (FuncAType)funcA_addr;
FuncBType funcB = (FuncBType)funcB_addr;
FuncBType funcB2 = (FuncBType)funcB_addr2;
int a = funcA(&classA, 0x21);
double b = funcB(&classA);
double b2 = funcB2(&classA);
PRINT(a);
PRINT(b);
PRINT(b2);
return 0;
}
输出结果如下:
-
vptr:虚表指针,指向虚表,解引用后即可获得虚表的地址值。
-
vtable_addr:虚表地址,存放在vptr所指向的内存中。
-
virtual_func_ptr:将虚表地址vtable_addr强转为指针,指向虚函数地址。
-
funcA_addr:将virtual_func_ptr解引用,即得到第一个虚函数的地址。
-
funcB_addr:将virtual_func_ptr + 1解引用,即得到第二个虚函数的地址。
-
funcB_addr2:将vtable_addr + sizeof(intptr_t),先强转为指向虚函数地址的指针,再解引用,即得到第二个虚函数的地址,对于32为系统sizeof(intptr_t)的值为4,64位系统则等于8。从输出结果中可以看出,funcB的地址值是0x400e88,funcA的地址值是0x400e78,两者的差值正好是8,所以此处sizeof(intptr_t)的值为8。
-
对于funcB_addr和funcB_addr2的理解:
-
我们可以按照如下方式去理解:
int* p = new int[4]{1, 2, 3, 4}; int p0 = *p; // 第一个元素,很容易理解 int p1 = *(p + 1); // 第二个元素,很容易理解 intptr_t addr = reinterpret_cast<intptr_t>(p); // 得到指针p的地址值 // 每4个(sizeof(int)为4)字节存放一个元素,所以addr+4*i即表示第i+1个元素的地址。 int p1_2 = *reinterpret_cast<int*>(addr + sizeof(int)); // 第二个元素的值 PRINT(p0); // 输出1 PRINT(p1); // 输出2 PRINT(p1_2); // 输出2
funcB_addr就类似p1,funcB_addr2类似于p1_2,virtual_func_ptr类似于p,vtable_addr类似于addr,虚函数地址即为元素,每个元素占sizeof(intptr_t)个字节。
-
虚函数在虚表中的偏移值
auto funcA_ptr = &ClassA::funcA;
auto funcB_ptr = &ClassA::funcB;
intptr_t funcA_offset = reinterpret_cast<intptr_t>((intptr_t&)funcA_ptr);
intptr_t funcB_offset = reinterpret_cast<intptr_t>((intptr_t&)funcB_ptr);
PRINT(funcA_offset); // 输出1,实际偏移值为0
PRINT(funcB_offset); // 输出9,实际偏移值为8
-
可以看出funcA的偏移值是funcA_offset - 1,funcB的偏移值是funcB_offset - 1。
-
可以通过上述偏移值和虚表指针来确定虚函数的实际地址。而不是通过给定值。