浅谈C++对象内存布局

最简单的类

先从一个简单的类开始吧。如下,此简单类,非常简单,两个int成员,通过printf很容易了解到它的内存布局,本质就是一个C结构体,两个成员依次排列。

对象:|成员1 | 成员2 |  

<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Class0 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span><span class="kwrd" style="color:#00ff;">int</span> member1; </span>
   6:  int member2; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span>}; </span>
   8:  int main() 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span>{ </span>
  10:  Class0 c; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>printf(<span class="str" style="color:#06080;">"object addr=0x%lx\nmember1 addr=0x%lx\nmember2 addr=0x%lx\n"</span>, </span>
  12:  &c, &c.member1, &c.member2); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span><span class="kwrd" style="color:#00ff;">return</span> 0; </span>
  14:  } 
# ./a.<span class="kwrd" style="color:#00ff;">out</span> 
object addr=0x7fffea480d70  
member1 addr=0x7fffea480d70 <span class="rem" style="color:#0800;">//类成员1 </span>
member2 addr=0x7fffea480d74 //类成员2 

成员函数

那么我们增加点复杂性,添加一个成员函数。 

<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Class1 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>   <span class="kwrd" style="color:#00ff;">int</span> member1; </span>
   6:     int member2; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span>   <span class="kwrd" style="color:#00ff;">void</span> function1() { printf(<span class="str" style="color:#06080;">"Class1::function1"</span>); } </span>
   8:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span><span class="kwrd" style="color:#00ff;">int</span> main() </span>
  10:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>   Class1 c; </span>
  12:     printf("object    addr=0x%lx\n", &c); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>   printf(<span class="str" style="color:#06080;">"member1   addr=0x%lx\n"</span>, &c.member1); </span>
  14:     printf("member1   addr=0x%lx\n", &c.member2); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>   printf(<span class="str" style="color:#06080;">"function1 addr=0x%lx\n"</span>, &Class1::function1); </span>
  16:     return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span>} </span>
./a.<span class="kwrd" style="color:#00ff;">out</span> 
object  addr =0x7fff6805bf90 
member1 addr=0x7fff6805bf90 
member1 addr=0x7fff6805bf94 
function1 addr=0x4006a0<span class="rem" style="color:#0800;">//成员函数地址在代码段。----这简直是废话,不在代码段没法玩啊。 </span>
对象:    | 成员1 | 成员2 |   
代码段:  |成员函数| 
我们看到,对象数据成员的布局并没有变化,但是函数成员的地址跑到十万八千里之外了。为什么?很简单,因为函数是代码,放在了代码段。这也是我们通过Class1::function1来取值,而不是c.function1的原因。从这里可以看出,类的函数成员本质就是一个C全局函数,那么如果函数内访问类的非静态数据成员,如何动态的获取成员地址?编译器是这样做的: 
1. 编译器生成function1()的指令时,如果遇到了访问对象的数据成员,比如member1,就从一个约定的位置(比如一个寄存器)获取对象的首地址(其实就是this指针),然后加上偏移(这个是编译时期可以确定的),也就找到了member1对应的内存位置,就可以访问member1了。 
2. 编译器生成c.function1()对应的指令时,把c的地址,放到了上述约定的位置。 
简单来说,c.function1() 等价于function1(c), c是作为隐含参数传递给function1了。 

虚函数

好了搞清楚了成员函数的工作机制,我们再进一步分析,如下例子,有了继承,并且基类成员函数是一个虚函数。派生类重载了它。 

<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Base 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>   <span class="kwrd" style="color:#00ff;">virtual</span> <span class="kwrd" style="color:#00ff;">void</span> function() { printf(<span class="str" style="color:#06080;">"Base::function1\n"</span>); } </span>
   6:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span><span class="kwrd" style="color:#00ff;">class</span> Derived : <span class="kwrd" style="color:#00ff;">public</span> Base </span>
   8:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span><span class="kwrd" style="color:#00ff;">public</span>: </span>
  10:     void function() { printf("Derived::function1\n"); } 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>}; </span>
  12:  int main() 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>{ </span>
  14:     printf("Base::function addr    = 0x%lx\n", &Base::function); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>   printf(<span class="str" style="color:#06080;">"Derived::function addr = 0x%lx\n"</span>, &Derived::function); </span>
  16:     Base* pb = new Derived(); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span>   pb->function(); </span>
  18:     return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  19:  </span>} </span>
./a.<span class="kwrd" style="color:#00ff;">out</span> 
Base::function addr    = 0x1 
Derived::function addr = 0x1 
Derived::function1 
我们看看它的输出,成员函数的地址是0x1,这明显不是一个合法的地址,更像是一个偏移量,为什么?暂且先不管为什么地址是0x1,不妨先分析下,下面这两行代码是如何工作的。 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>Base* pb = <span class="kwrd" style="color:#00ff;">new</span> Derived(); </span>
   2:  pb->function(); 

派生类指针赋给了基类指针,调用function,但执行还是派生类的function,这就是多态了。那么对于 pb->function(); 这个语句来说,编译器是不能够在编译时期决定调用哪个function的。因为它并不知道pb这个指针是通过派生类转化而来。大家会说,我们上面的语句不是告诉它了吗?这个肯定不行,编译器不能做这个上下文关联,你要是通过函数参数传递过来,赋值的地方离这条语句很远甚至都不在一个源文件里面怎么办?所以这个决定调用哪个function的信息,必须保存在内存里面,运行期间就可以执行正确的函数。那么具体保存在哪里?如何工作的?gcc是这样做的: 
1. 申请一段内存,存放虚函数的地址。就是一些书上所说的虚表。本质就是一个数组。 
2. 在对象的起始位置,存放虚表首地址,而不是像普通类对象那样存放第一个非静态数据成员。 
3.  pb->function(); 这条语句执行时,编译器知道function是一个虚函数(我们声明了virtual关键字),那么就会采用虚函数的调用方法,首先根据pb找到虚表的首地址,然后加上一个偏移量,因为是编译器把function这个函数的地址放到虚表内的,所以它知道偏移量。我们通过下面这段代码验证这点: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Base 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>   <span class="kwrd" style="color:#00ff;">virtual</span> <span class="kwrd" style="color:#00ff;">void</span> function1() { printf(<span class="str" style="color:#06080;">"Base::function1\n"</span>); } </span>
   6:     virtual void function2() { printf("Base::function2\n"); } 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span>}; </span>
   8:  int main() 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span>{ </span>
  10:     printf("Base::function addr    = 0x%lx\n", &Base::function1); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>   printf(<span class="str" style="color:#06080;">"Base::function addr    = 0x%lx\n"</span>, &Base::function2); </span>
  12:     Base* pb = new Base; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>   <span class="kwrd" style="color:#00ff;">long</span>* vtl = *(<span class="kwrd" style="color:#00ff;">long</span>**)pb;  </span>
  14:     printf("0x%lx\n", *(vtl)); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>   printf(<span class="str" style="color:#06080;">"0x%lx\n"</span>, *(vtl + 1)); </span>
  16:     return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span>} </span>
# ./a.<span class="kwrd" style="color:#00ff;">out</span> 
Base::function addr    = 0x1 
Base::function addr    = 0x9 
0x40082a 
0x400812 
# nm a.out | grep function 
000000000040082a W _ZN4Base9function1Ev 
0000000000400812 W _ZN4Base9function2Ev 
# c++filt _ZN4Base9function1Ev 
Base::function1() 
# c++filt _ZN4Base9function2Ev 
Base::function2() 
对象:    | 虚表地址|成员1 | 成员2 |   
虚表:    |虚函数1的地址|虚函数2的地址| 
代码段:  |虚函数1|虚函数2| 
1. 通过long* vtl = *(long**)pb; 获取pb对象第一个成员的内容,我们拿到了虚表的首地址vtl。  
2. printf("0x%lx\n", *(vtl)); 访问虚表的第一个元素,打印的是0x40082a,恰好对应我们通过nm查看到的Base::function1的函数地址000000000040082a 。 
3. printf("0x%lx\n", *(vtl + 1)); 访问虚表的第二个元素,打印的是0x400812,恰好对应我们通过nm查看到的Base::function2的函数地址0000000000400812 。 
那么&Base::function1是0x1,&Base::function2是0x9,何解?其实怎么解读,完全看编译器心情。。。从我们的实验结果来看,gcc是把它解读成了虚表偏移量+1。编译器也是可以解读为函数的真实地址的。 
所谓多态,也就是这么回事儿,其逻辑并不复杂,只是C++"封装"了细节,只给我们展示了它的强大形象,让我们觉得多态好神奇啊,其实丫的,本质就是函数指针,就是地址而已,因为地址才是CPU理解的东西。懂得这点,就知道内核里到处都是多态,同样是一个read操作,read不同的文件,执行不同的函数。。。内核就是在文件对象(C结构体)里,保存了函数指针,不同的文件系统注册不同的函数指针。内核是各种编程技术、思想的集大成者,OO思想随处可见。 

单继承

扯远了,我们还是继续回到C++,多态背后的内存布局讲完了,我们再进一步分析类继承的。当派生类继承了基类,也就拥有了基类的数据成员,那么这些数据成员如何摆放?其实还能怎么摆放?无非是顺着来就好了。对就这样,但是谁先谁后?是否顺序无所谓?答案是,基类在前面派生类在后边更合理。为什么是这样?我们考虑下面的代码: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span><span class="kwrd" style="color:#00ff;">class</span> Base </span>
   2:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span><span class="kwrd" style="color:#00ff;">public</span>: </span>
   4:  int b; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>}; </span>
   6:  class Derived : public Base 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span>{ </span>
   8:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span><span class="kwrd" style="color:#00ff;">int</span> d; </span>
  10:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span><span class="kwrd" style="color:#00ff;">int</span> main() </span>
  12:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>Derived d; </span>
  14:  d.b = 2012; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>Base* b = &d; </span>
  16:  b->b = 2012; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span>} </span>
我们把一个派生类对象地址,赋给基类指针,并且通过它访问基类成员,如果派生类对象的内存布局是,基类在后,即成员d在前面,然后成员b。    d.b = 2012; 这条语句没有问题,编译器知道b的位置,d起始位置加4即可。但是 b->b = 2012; 就没法玩了,因为编译器不知道b是一个Derived对象(原因上面有说),那么它就按b在Base中的偏移0,来算,而这个偏移,取到的其实是Derived::d的内容。如果反过来放,就没问题了。通过下面的代码,我们可以看到,确实是按基类优先的顺序存放的。 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Base 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>   <span class="kwrd" style="color:#00ff;">int</span> b; </span>
   6:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span><span class="kwrd" style="color:#00ff;">class</span> Derived : <span class="kwrd" style="color:#00ff;">public</span> Base </span>
   8:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span><span class="kwrd" style="color:#00ff;">public</span>: </span>
  10:     int d; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>}; </span>
  12:  int main() 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>{ </span>
  14:     Derived d; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>   printf(<span class="str" style="color:#06080;">"Derived  = 0x%lx\n"</span>, &d); </span>
  16:     printf("Derived.b  = 0x%lx\n", &d.b); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span>   printf(<span class="str" style="color:#06080;">"Derived.d  = 0x%lx\n"</span>, &d.d); </span>
  18:     return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  19:  </span>} </span>
# ./a.<span class="kwrd" style="color:#00ff;">out</span> 
Derived    = 0x7fffece35bf0 
Derived.b  = 0x7fffece35bf0 
Derived.d  = 0x7fffece35bf4 

对象:    | 基类的成员|派生类的成员|   

多继承

终于来到了最神奇的地方,那就是多继承,在讨论多继承的内存布局之前,我忍不住要吐槽几句。C++的设计哲学是大而全,实际上很多特性可能一辈子都用不到,我觉得一个好的编程语言,应该提供简洁的语言特性和强大丰富的功能库,比如Python。C++太不精简了。实际上,所有C++程序其实都是C++子集程序员;但所有C程序员都是C全集程序员。C的语言特性基本没有多余的,C程序员基本都会用到。多继承就是最多余的C++特性之一。可能有些同学说,有些地方用多继承很方便,不用不太好搞;没这回事儿,那肯定是类设计出了问题,正是因为语言支持这种特性,才导致一些糟糕的设计存在。要是C++不支持,编译器编译不过,你丫的会想不出来解决方案?好的语言特性可以直接引导程序员好的设计思维。比如Erlang不支持循环、不支持变量二次赋值…… 逼得程序员完全改变思维方式。。。结果就是写出来的程序,自然支持多核、高并发,还无锁。另外你看google的C++编程规范就知道,最重要的一部分就是对C++做减法,取子集。吐槽完毕,可能引起一片拍砖。。。(偶尔还是得抛一些观点,否则只是纯技术性的,太冷清了) 
单继承的内存布局,是基类成员在前,派生在后,但是多继承呢?丫的有两个基类,谁前谁后?谁前谁后不重要,关键的是根据上面单继承分析,如果基类成员在派生类对象的位置不是从头开始,派生类对像指针转化为基类指针之后,就不能正确访问基类成员了。而多继承,必然至少有一个基类不是从头开始的。那么怎么办?还能怎么办,凉拌!当你把一个派生类对象地址赋值给一个基类指针,如果这个基类在派生类中的位置,不是从头开始的,编译器偷偷的把它改变,加上基类在派生类中的位置偏移量!我们来验证下: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Base1 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span>   <span class="kwrd" style="color:#00ff;">int</span> b1; </span>
   6:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span><span class="kwrd" style="color:#00ff;">class</span> Base2 </span>
   8:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span><span class="kwrd" style="color:#00ff;">public</span>: </span>
  10:     int b2; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span>}; </span>
  12:  class Derived : public Base1, public Base2 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>{ </span>
  14:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>   <span class="kwrd" style="color:#00ff;">int</span> d; </span>
  16:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span><span class="kwrd" style="color:#00ff;">int</span> main() </span>
  18:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  19:  </span>   Derived d; </span>
  20:     printf("Derived  = 0x%lx\n", &d); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  21:  </span>   printf(<span class="str" style="color:#06080;">"Derived.b1  = 0x%lx\n"</span>, &d.b1); </span>
  22:     printf("Derived.b2  = 0x%lx\n", &d.b2); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  23:  </span>   printf(<span class="str" style="color:#06080;">"Derived.d  = 0x%lx\n"</span>, &d.d); </span>
  24:     Base2* b2p = &d; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  25:  </span>   printf(<span class="str" style="color:#06080;">"Base2 pointer = 0x%lx\n"</span>, b2p); </span>
  26:     return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  27:  </span>} </span>
 
# ./a.<span class="kwrd" style="color:#00ff;">out</span> 
Derived          =    0x7fffedfe10e0 
Derived.b1      =    0x7fffedfe10e0 
Derived.b2      =    0x7fffedfe10e4 
Derived.d        =    0x7fffedfe10e8 
Base2 pointer  =    0x7fffedfe10e4 

可以看到,摆放的顺序是Base1,Base2,Derived: 
对象:| 基类1的成员 | 基类2的成员 | 派生类的成员 
而当我们把Derived的地址0x7fffedfe10e0赋给Base2时,变成了0x7fffedfe10e4,即Base2成员的起始位置,这样我们的b2p->b2; 可以正确的工作。是不是很神奇?=号都是不可信的! 

多继承+虚函数

如果在多继承的基础上有加上了虚函数怎么办?也就说多了一个虚表,假设两个基类,gcc是这样处理的: 
对象:| 虚表1的地址 | 基类1的成员 | 虚表2的地址 | 基类2的成员 | 派生类的成员 
其中虚表1中存放是派生类重载的虚函数地址,无论来自于基类1还是基类2。虚表2只存放基类2的重载函数地址(实际上GCC帮你生成了一个中间函数,中间函数再去调用实际的函数)。 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   1:  </span>#include <cstdio> </span>
   2:  class Base1 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   3:  </span>{ </span>
   4:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   5:  </span><span class="kwrd" style="color:#00ff;">int</span> b1; </span>
   6:  virtual void function1() { printf("Base1::function1\n"); } 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   7:  </span>}; </span>
   8:  class Base2 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">   9:  </span>{ </span>
  10:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  11:  </span><span class="kwrd" style="color:#00ff;">int</span> b2; </span>
  12:  virtual void function2() { printf("Base2::function2\n"); } 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  13:  </span>}; </span>
  14:  class Derived : public Base1, public Base2 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  15:  </span>{ </span>
  16:  public: 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  17:  </span><span class="kwrd" style="color:#00ff;">int</span> d; </span>
  18:  void function1() { printf("Derived::function1\n"); } 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  19:  </span><span class="kwrd" style="color:#00ff;">void</span> function2() { printf(<span class="str" style="color:#06080;">"Derived::function2\n"</span>); } </span>
  20:  }; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  21:  </span><span class="kwrd" style="color:#00ff;">int</span> main() </span>
  22:  { 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  23:  </span>Derived d; </span>
  24:  printf("Derived = 0x%lx\n", &d); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  25:  </span>printf(<span class="str" style="color:#06080;">"Derived.b1 = 0x%lx\n"</span>, &d.b1); </span>
  26:  printf("Derived.b2 = 0x%lx\n", &d.b2); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  27:  </span>printf(<span class="str" style="color:#06080;">"Derived.d = 0x%lx\n"</span>, &d.d); </span>
  28:  Base2* b2p = &d; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  29:  </span>printf(<span class="str" style="color:#06080;">"Base2 pointer = 0x%lx\n"</span>, b2p); </span>
  30:  long* vtl = *(long**)b2p; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  31:  </span>printf(<span class="str" style="color:#06080;">"0x%lx\n"</span>, *(vtl)); </span>
  32:  printf("0x%lx\n", *(vtl + 1)); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  33:  </span>vtl = *(<span class="kwrd" style="color:#00ff;">long</span>**)&d; </span>
  34:  printf("0x%lx\n", *(vtl)); 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  35:  </span>printf(<span class="str" style="color:#06080;">"0x%lx\n"</span>, *(vtl + 1)); </span>
  36:  return 0; 
<span style="font-family:微软雅黑;"><span class="lnum" style="color:#606060;">  37:  </span>} </span>
# ./a.<span class="kwrd" style="color:#00ff;">out</span> 
Derived = 0x7fffa74ae400 
Derived.b1 = 0x7fffa74ae408<span class="rem" style="color:#0800;">//b1没有放在最开始,因为第一个是虚表地址 </span>
Derived.b2 = 0x7fffa74ae418//b2没有放在b1后面,因为前边还有一个虚表地址 
Derived.d = 0x7fffa74ae41c 
Base2 pointer = 0x7fffa74ae410//base2在派生类中的起始位置, 
0x4008aa<span class="rem" style="color:#0800;">//虚表2中存放的函数地址,gcc生成的中间函数 </span>
0x0//虚表2中存放的函数地址&nbsp; 
0x4008c8<span class="rem" style="color:#0800;">//虚表1中存放的函数地址,function1 </span>
0x4008b0//虚表1中存放的函数地址,function2 
# nm a.<span class="kwrd" style="color:#00ff;">out</span> |grep function 
00000000004008e0 W _ZN5Base19function1Ev 
00000000004008f8 W _ZN5Base29function2Ev 
00000000004008c8 W _ZN7Derived9function1Ev 
00000000004008b0 W _ZN7Derived9function2Ev 
00000000004008aa W _ZThn16_N7Derived9function2Ev 
# c++filt _ZN7Derived9function1Ev _ZN7Derived9function2Ev _ZThn16_N7Derived9function2Ev 
Derived::function1() 
Derived::function2() 
non-virtual thunk to Derived::function2() 
# objdump -d a.<span class="kwrd" style="color:#00ff;">out</span> | sed -n <span class="str" style="color:#06080;">'/_ZThn16_N7Derived9function2Ev/,/00000/p'</span> 
00000000004008aa <_ZThn16_N7Derived9function2Ev>: 
4008aa: 48 83 c7 f0 add $0xfffffffffffffff0,%rdi 
4008ae: eb 00 jmp 4008b0 <_ZN7Derived9function2Ev>//中间函数跳转到了function2 
00000000004008b0 <_ZN7Derived9function2Ev>: 

了解C++内存布局的意义

意义至少有一点,让我们写出更好的C++程序。内存布局越复杂,性能越差,所以你会知道该如何选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值