在c里面有个函数是offsetof,提供结构体里偏移量计算,你查看官网定义发现这个宏是这样写的
#undef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #endif /* __KERNEL__ */
这里看到它是将一个0地址转成TYPE的对象指针,在用指针的变量成员的地址就是该偏移量了。这里你也可以将0改成别的数字,只要减去头就可以,当然这里指的是不超过范围的。
原理介绍到这里,现在我们来玩一下变形。
class A{ public: int publicA; int publicB; private: int a=9; void JustTest(){ cout << "this is A, a is "<<a<<endl; }; }; #define MyOffset(type, mem) ((size_t)&((type*)0)->mem )
这时如果我要求A相对于a的偏移是否能利用这个原理呢?答案是不能的,因为这个原理是根据a可以给指针访问而得到的。那么我们又想知道该类的成员偏移量怎么办呢?这里我暂时想到以下方法
class A{ public: int publicA; int publicB; private: int a=9; void JustTest(){ cout << "this is A, a is "<<a<<endl; }; }; class B{ public: int publicA =1; int publicB=2; int a= 10; void JustTest(){ cout << "this is B, a is "<<a<<endl; }; }; #define MyOffset(type, mem) ((size_t)&((type*)0)->mem )
B是A的一样的代码,只是权限不同,这就相当于计算了A的偏移变量。这时候我们思考一个问题,如果我们能用B的分布去推算A,那么能不能也同时修改A的私有变量呢?答案是可以的,代码如下
B* test; B testb; A testA; test = &testb; test->JustTest(); test = (B*)&testA; test->JustTest(); int* changeA = (int*)((char*)(&testA )+8); cout <<"changeA " <<*changeA<<endl; *changeA = 11; test->JustTest();
通过上述,我们发现我们修改了A的变量,但是在调用B的方法时,我们还是在B的JustTest里面,现在我们再思考下能不能调用A的私有方法?这个暂时还在思考。。。以后想到再写。
上述我们讲到怎么样修改classA的private变量,现在我们开始说回类里函数的调用问题。虽然还是没有解决那个方法,但也总结一下,当作这几天的积累,代码先奉上
#include <iostream> #include <stdio.h> typedef unsigned long DWORD; class tt { public: void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall. { printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s); foo2(x,c,s); } void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定. { printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s); } int m_a =895; }; class Privatett { void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall. { printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s); } void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定. { printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s); } int m_a =895; }; template <class ToType, class FromType> void GetMemberFuncAddr_VC6(ToType& addr,FromType f) { union { FromType _f; ToType _t; }ut; ut._f = f; addr = ut._t; } int main() { // int a=100,b=200; // int result =3; // // __asm__ ("movl %1,%0 \n\t" // "movl $1028,%1\n\t" // "movl %1,%0": "=r" (result) : "m" (b)); // // std::cout << "Hello, World! " << result<< std::endl; // __asm__ ("movl %1,%0 \n\t" // "movl $1028,%%eax\n\t" // "movl %%eax,%0": "=r" (result) : "m" (b)); // return 0; typedef void (__attribute__((__stdcall__)) *FUNCTYPE) ( void * , int,char,char*);//定义对应的非成员函数指针类型,注意指定__stdcall. typedef void (__attribute__((__stdcall__)) *FUNCTYPE2)(void *,int,char,char*);//注意多了一个void *参数. tt abc; abc.m_a = 123; DWORD ptr; DWORD This = *(DWORD*)&abc; DWORD Thest = (DWORD)&abc.m_a; GetMemberFuncAddr_VC6(ptr,&tt::foo); //取成员函数地址. std::cout << "before "<< Thest <<std::endl; FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//将函数地址转化为普通函数的指针. __asm__ __volatile__("movq %1,%0\n\t" "movq %0, %%rcx" : "=r" (This) : "m" (abc)); // __asm__ ("movl $This, %ecx"); // __asm__("movl %esp,%eax"); //看起来很熟悉吧! fnFooPtr(&abc,5,'e',"7xyz"); //象普通函数一样调用成员函数的地址. GetMemberFuncAddr_VC6(ptr,&tt::foo2); //取成员函数地址. FUNCTYPE2 fnFooPtr2 = (FUNCTYPE2) ptr;//将函数地址转化为普通函数的指针. fnFooPtr2(&abc,5,'a',"7xyz"); //象普通函数一样调用成员函数的地址,注意第一个参数是this指针. Privatett testd3; fnFooPtr(&testd3,5,'e',"7xyz"); //象普通函数一样调用成员函数的地址. fnFooPtr2(&testd3,5,'a',"7xyz"); //象普通函数一样调用成员函数的地址,注意第一个参数是this指针. std::cout << "Hello, World!" << std::endl; return 0; }
这里看到类的普通成员函数里面是没有跟随类的(这里的普通成员函数不包括虚函数),它的函数地址因为不跟随类,所以没有办法计算到私有的普通函数(暂时是我个人没有研究到),但在普通public函数我们能计算到,而且它的地址接入多了一个this的指针,这里你可以看到(void*)这个就是this,按照这个原理,我们可以这样想,如果this可以换成别的对象,会产生什么好玩的事情。上述的例子里就是接tt类的成员函数,但我们传递的是testd3的对象,暂时研究到这里,至于怎样获取类的私有函数,以后有什么发现再补上。
补上上述的私有成员函数的地址以及调用方法,老规矩,代码先上:
//原理:模板动态生成而逃过静态编译的问题 template<typename Tag> struct result { /* export it ... */ typedef typename Tag::type type; static type ptr; }; template<typename Tag> typename result<Tag>::type result<Tag>::ptr; //声明指针 template<typename Tag, typename Tag::type p> struct rob : result<Tag> { //继承result /* fill it ... */ struct filler { filler() { result<Tag>::ptr = p; } //将指针复制 }; static filler filler_obj; }; template<typename Tag, typename Tag::type p> typename rob<Tag, p>::filler rob<Tag, p>::filler_obj; //filler_obj 静态对象 struct A { public: void sayit(){std::cout << "My a is " <<a <<std::endl;} private: void f() { std::cout << "proof! ((((" << std::endl; a += 10; } int a=0; }; struct Af { typedef void(A::*type)(); }; template class rob<Af, &A::f>; int main() { A a; a.sayit(); (a.*result<Af>::ptr)(); a.sayit(); }
这个是网上找回来的版本,挺复杂的一个例子,主要思想是用static找到函数地址。如果想看简化版,请看下面我写的
#include <iostream> #include <stdio.h> class Privatett { void foo1()//没有指定类型,默认是__thiscall. { printf("\n Privatett_m_a\n"); } void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall. { printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s); } void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定. { printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s); } int m_a =895; }; struct SaveType { typedef void(Privatett::*type)(); }; static SaveType::type testdFor ; template< typename SaveType::type p> struct ForTest{ struct filler { filler() { testdFor = p; std::cout << "TestdFor "<<std::endl;} //将指针复制 }; static filler filler_obj; }; template class ForTest<&Privatett::foo1>; template< typename SaveType::type p> typename ForTest<p>::filler ForTest<p>::filler_obj; int main() { Privatett testd3; (testd3.*testdFor)(); std::cout << "Hello, World!" << std::endl; return 0; }
template class ForTest<&Privatett::foo1>; 这里要注意,关键就在这里。
先看看template class的意思是显式实例化,这里显示实例化的意思是显式实例化只需声明,不需要重新定义。编译器根据模板实现实例声明和实例定义,这里主要思路是借用了显示实例化的实例化,那样编译器会将Privatett::foo1的实例化的代码写好(这里只是口头这样说,真正的说法可以搜搜看显式实例化),而我们没有调用,这个是关键。但里面有个static的变量,static是总存在内存,所以没有选择,编译器会将static的变量初始化,这一切就理所当然的我们将地址拿到了static的变量里面,这里的static是public,就算不是我们也能计算到。而此时,我们拿到了private方法的地址,就可以像普通函数调用也可以。
typename ForTest<p>::filler ForTest<p>::filler_obj;就是初始化对象,从而拿到地址,分析到这里结束,结束这个话题,这里的原理是模板的一个特性,只关心参数类型,不安全检测。
有什么疑问或者错漏欢迎发邮箱zoring@126.com指出,感谢。