今天在公司看C++代码时看到了一段很新奇的用法, 回来剖析了一下, 感觉虽然原理很简单还是值得记录一下的。
代码大概是这个样子的:
class test {
public:
void a1(){printf("test::a1\n");};
}
int main()
{
test *t = new test();
void (test::*a)(void);
a = &test::a1;
(t->*a)();
}
以前在C++中用函数指针时,从来都反射性的去用静态成员函数, 却从来都没想到原来普通的成员函数也是可以使用函数指针的。
仔细分析一下, 原理也早就知道。 无非就是this指针的事。
使用test对象的指针去调用某函数与普通的C函数指针惟一的区别就是, 会将test对象的指针作为this指针通过ecx寄存器来传入, 那么所有的C++类成员函数在本质上也是通过ecx的值来访问他们的成员变量。
在不反汇编的情况下, 为了更清晰的验证一下上述结论, 加入了如下代码:
void (*b)(void);
b = (void (*)(void))&test::a1;
b();
最后可以发现函数依然可以运行。
当然这里其实有点取巧,因为test::a1函数并没有访问类的成员变量, 所以我们可以将此成员函数直接作为普通的函数来调用。
如果test::a1函数访问成员变量的话, 由于C函数调用时并没有传入this指针, 那么test::a1函数必将会由于随机的ecx值而导致崩溃。
但是这段代码已经足够证明,&test::a1给出的是函数的绝对地址与普通的C函数的地址并无两样,那么调用b()和(t->*a)()的区别其实就在于是否传入了this指针。
如果再有点hack精神, 其实可以去将test::a1函数更改为访问成员变量, 然后在调用C函数前, 可以手动内嵌asm代码来将ecx值强制改为t的指针值, 如果效果一样, 那么即可完全证明上述结论。
btw, 其实最快的证明方法就是使用gcc生成一下汇编代码一看便知。 手动内嵌asm的事却颇像乌鸦去河边捡石子喝水的故事。