通常我们访问结构体或类的成员变量,使用的是比较普通的方法。如定义一个struct
- struct A
- {
- char a;
- int b;
- double c;
- void (*func) (A *);
- };
那么我们访问结构体中的成员有两种方法:1)(结构体对象名) . (成员变量名);2) (结构体指针) -> (成员变量名)。由于任何变量都在内存中对应一个地址,我们是不是可以通过指针地址的方式访问结构体的成员变量呢?
首先我们举一个简单的例子:
- struct example
- {
- int a;
- int b;
- };
- example ex;
- ex.a =1;
- ex.b = 12;
-
- cout<<showbase<<hex<<&ex<<endl;
- int *pa = (int*)&ex;
- cout<<*pa<<endl;
- int *pb = (int*)&ex +1;
- cout<<*pb<<endl;
-
下面就举一个稍微复杂一些的例子,也就是本文开头提到的结构体A,只不过改了下名字。
- struct Base
- {
- char a;
- int b;
- double c;
- void (*func_base)(Base*);
- };
该结构体包含了一个字符类型的变量a,整形变量b,双精度浮点型变量c,函数指针func。该结构体的内存大小是24个字节(考虑字节对齐)。那么如何实现利用地址访问结构体的成员变量呢?下面先给出实现访问该结构成员变量的代码,然后再分析该代码。
- void show_base(Base *base)
- {
- cout<<base->a<<" "<<base->b<<" "<<base->c<<endl;
- }
- Base new_base(char i, int j, double k)
- {
- Base base;
- base.a = i;
- base.b = j;
- base.c = k;
- base.func_base = show_base;
- return base;
- }
-
- int main()
- {
- cout<<sizeof(Base)<<endl;
- Base b = new_base('a', 15,1.56);
- cout<<"结构体赋值后的输出: ";
- b.func_base(&b);
- cout<<"结构体初始地址是:"<<showbase<<hex<<&b<<endl;
- char *pa = (char*)&b;
- cout<<"成员变量a的地址是:"<<showbase<<hex<<
- (int*)pa<<"; a的值是:"<<*pa<<endl;
-
- int *pb = (int*)&b +1;
- cout<<"成员变量b的地址是:"<<showbase<<hex<<
- (int*)pb<<"; b的值是:"<<*pb<<endl;
-
- double *pc = (double*)((int*)&b +2);
- cout<<"成员变量c的地址是:"<<showbase<<hex<<
- (int*)pc<<"; c的值是:"<<*pc<<endl;
-
- typedef void (*func)(Base *);
- func pfunc_base = (func)*(int*)((double*)&b + 2);
- cout<<"成员变量func_base的地址是:"<<showbase<<hex<<
- (int*)pfunc_base<<endl;
- cout<<"成员变量func_base的结果:";
- pfunc_base(&b);
- return 0;
- }
从输出可以看出来以上程序的结果是正确的,而且对每个成员变量的输出地址也能看出结构的内存布局。程序中主要注意两点:
1) 对指针地址进行加减运算时,实际的运算是加减该指针指向的变量类型的字节数*加减的值。
2) 提取函数指针变量的地址是要注意,结构的地址(&b)加上一定的偏移量得到的地址是
指向函数指针的指针,因此要对它做解地址操作,然后进行指针类型转换,即func pfunc_base = (func)*(int*)((double*)&b + 2);这个语句。
结构体的内存布局大致就是这样的,下面我们就讨论下更有意思的类的内存布局。由于类牵扯到继承,虚函数,多重继承,虚继承,因此类的内存布局比结构体复杂了很多。这里我们就一步一步的分析这些情况。
1) 首先分析单一继承的问题,并且先不考虑虚函数的情况,即类中只有成员变量,因为除了虚函数的成员函数都不会占用类的内存空间,所以我们可以忽略他们。下面就是一个简单的例子。
- class A
- {
- public:
- A(int i):a(i){}
- int a;
- };
- class B:public A
- {
- public:
- B(int i, int j):A(i),b(j){};
- int b;
- };
- int main()
- {
- B b(12,15);
- cout<<"类对象b的地址为: "<<&b<<endl;
- int *pa = (int*)(&b);
- cout<<"类对象b中继承自类A的成员变量a的地址:"
- <<showbase<<hex<<(int*)pa<<"其值为:"<<*pa<<endl;
-
- int *pb = (int*)((int*)(&b)+1);
- cout<<"类对象b中的成员变量b的地址:"
- <<showbase<<hex<<(int*)pb<<"其值为:"<<*pb<<endl;
- return 0;
- }
从上面的输出可以看出,在类的继承时,派生类的首地址指向了其继承自基类的成员变量;换句话说就是派生类中的内存布局是:先基类对象,再是派生类的成员变量。如果类内的成员变量的类型比较复杂,那么就应该考虑字节对齐的原则了。为了更好的表明类中的内存布局,在举一个例子说明下,在这里引入了虚函数,并且增加了类中的成员变量的类型:
- class A
- {
- public:
- A(int i,double da):a(i),da(da){}
- virtual void show()
- {
- cout<<"this is A::show!"<<endl;
- }
- int a;
- double da;
- };
- class B:public A
- {
- public:
- B(int i,double da, int j,double db)
- :A(i,da),b(j),db(db){};
- void show()
- {
- cout<<"this is B::show!"<<endl;
- }
- int b;
- double db;
- };
-
- int main()
- {
- B b(12,3.15,15,6.89);
- cout<<"类对象b的地址为: "<<&b<<endl;
- int *vptr = (int*)*(int*)(&b);
- cout<<"类对象b中指向虚函数表的指针地址是:"
- <<showbase<<hex<<(int*)vptr<<"其值为:"<<*vptr<<endl;
- cout<<"虚函数表的第一个函数的地址是: "<<*vptr<<endl;
- typedef void (*func)(void);
- func func_show;
- func_show = (func)*(int*)*(int*)&b;
- cout<<"转换后的虚函数表的第一个函数的地址是:"<<showbase<<hex<<(int*)func_show<<endl;
- func_show();
- int *pa = (int*)((int*)(&b)+1);
- cout<<"类对象b中继承自类A的成员变量a的地址:"
- <<showbase<<hex<<(int*)pa<<"其值为:"<<*pa<<endl;
-
- double *pda = (double*)((int*)(&b)+2);
- cout<<"类对象b中继承自类A的成员变量da的地址:"
- <<showbase<<hex<<(int*)pda<<"其值为:"<<*pda<<endl;
-
- int *pb = (int*)((int*)(&b)+4);
- cout<<"类对象b中的成员变量b的地址:"
- <<showbase<<hex<<(int*)pb<<"其值为:"<<*pb<<endl;
-
- double *pdb = (double*)((int*)(&b)+6);
- cout<<"类对象b中的成员变量db的地址:"
- <<showbase<<hex<<(int*)pdb<<"其值为:"<<*pdb<<endl;
- return 0;
- }
其输出结果是:
有虚函数的单一继承的派生类中的内存布局:首先是指向虚函数表的指针(放在首位是为了提高效率),其次是基类的成员变量,最后是派生类的成员变量。注:在对虚函数的指针进行转换时一定要注意:类的内存存放的是vptr指针,而vptr是指向虚函数表的指针,对vptr解地址运算,即*vptr,得到的是虚函数表中的第一个指针,但这个指针还不是函数指针,应该在对其做一个解地址运算,并做类型转换,即上述例程中的func_show = (func)*(int*)*(int*)&b;。
多重继承的情况
对于多重继承,首先要明确多重继承和虚函数的关系。对于单一继承来说,如果class B继承自class A;且A中有虚函数,因此class B中也有一个指向虚函数表的指针。但是对于多重继承,派生类会有多个虚函数表的指针,因为它的每个父类都有可能有虚函数。举个例子来说明下吧:
- class A
- {
- private:
- int a;
- public:
- A(int i):a(i){}
- virtual void print_A1()
- {
- cout<<"A::print_A1()!"<<endl;
- }
- virtual void print_A2()
- {
- cout<<"A::print_A2()!"<<endl;
- }
- };
- class B
- {
- private:
- int b;
- public:
- B(int j):b(j){}
- virtual void print_B1()
- {
- cout<<"B::print_B1()!"<<endl;
- }
- virtual void print_B2()
- {
- cout<<"B::print_B2()!"<<endl;
- }
- };
-
- class C
- {
- private:
- int c;
- public:
- C(int k):c(k){}
- virtual void print_C1()
- {
- cout<<"C::print_C1()!"<<endl;
- }
- virtual void print_C2()
- {
- cout<<"C::print_C2()!"<<endl;
- }
- };
- class D:public A, public B,public C
- {
- public:
- D(int i, int j, int k):A(i),B(j),C(k){}
- };
-
- typedef void (*func)(void);
-
- int main()
- {
- cout << sizeof(D) << endl;
- D d(12,16,25);
- cout<<showbase<<hex<<"类D对象d的内存地址: "<<(int*)&d<<endl;
- func func1, func2, func3,func4,func5,func6;
- func1 = (func)*(int*)*(int*)&d;
- cout<<"第一个指向虚函数表的指针地址: "<<(int*)&d<<endl;
- cout<<"基类A的第一个虚函数在虚函数表中的地址:"<<(int*)*(int*)&d<<endl;
- func1();
- func2 = (func)*((int*)*(int*)&d +1);
- cout<<"基类A的第二个虚函数在虚函数表中的地址:"<<(int*)*(int*)&d +1<<endl;
- func2();
-
- cout<<"第二个指向虚函数表的指针地址: "<<(int*)&d +2<<endl;
- func3 = (func)*(int*)*((int*)&d+2);
- cout<<"基类B的第一个虚函数在虚函数表中的地址:"<<(int*)*((int*)&d+2)<<endl;
- func3();
- func4 = (func)*((int*)*((int*)&d+2)+1);
- cout<<"基类B的第二个虚函数在虚函数表中的地址:"<<(int*)*((int*)&d+2) + 1<<endl;
- func4();
-
-
- cout<<"第三个指向虚函数表的指针地址: "<<(int*)&d +4<<endl;
- func5 = (func)*(int*)*((int*)&d+4);
- cout<<"基类C的第一个虚函数在虚函数表中的地址:"<<(int*)*((int*)&d+4)<<endl;
- func5();
- func6 = (func)*((int*)*((int*)&d+4)+1);
- cout<<"基类C的第二个虚函数在虚函数表中的地址:"<<(int*)*((int*)&d+4) + 1<<endl;
- func6();
- return 0;
- }
上面的输出结果是:
从上面输出结果可以知道:
1. 类D分别继承自类A,类B,类C;它总共有3个虚函数表。因此它的总得内存大小为24个字节。
2. 后面的内容分别用地址访问到了类D中的继承的所有虚函数,从继承的方式上也能看出类D中是存在三个虚函数表的。
3. 上面没有对类D中的变量进行地址的访问,不过这些可以依照上面提到的进行提取他们的地址。
4、 类D的内存布局是:
类A中的虚函数表指针
类A中的成员变量