关于结构体偏移量计算

在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指出,感谢。

posted @ 2017-10-13 14:19  ouyang_hang  阅读(572)  评论(0编辑  收藏  举报