【转】C++类的实例分布

原文:http://blog.chinaunix.net/u2/67984/showart_1915540.html

#include <stdio.h>
 const char * global_text="aaaa";
 class empty
 {
 virtual void sayHello(){printf("hello world\n");}
 };
 class Base
 {
 public:
 Base(int i1,char i2,short i3,char i4):i(i1),j(i2),k(i3),l(i4){}
 void print(){printf("hello world\n");};
 virtual int work(int i){printf("this is base work %d\n",i);}
 virtual void work2(){printf("this is base work2\n");}
 virtual void work3(){printf("this is base work3\n");}
 static int wangfei;
 private:
 int i;
 char j;
 short k;
 char l;
 };
 int Base::wangfei=8888;
 int global_data=7777;
 class Derive:public Base
 {
 public:
 Derive(int i1,char i2,short i3,char i4):Base(i1,i2,i3,i4){}
 int work(int);
 static void print(){printf("this is derive print\n");}
 virtual void d_work1(){}
 };
 int Derive::work(int i)
 {
     printf("this is derive work %d\n",i);
     return 10;
 }
 int main(int argc,char **argv)
 {
 char *test="hello world\n";
 empty e;
 printf("the size of e is %d\n",sizeof(e));
 printf("sizeof the test is %d\n",sizeof(test));
 Derive d(2,'c',3,'d');
 Base b(10,'a',20,'b');
 b=d;
 printf("sizeof b is %d,sizeof d is %d\n",sizeof(b),sizeof(d));
 b.work(1);
 d.work(1);
 Base *pointB=new Derive(4,'e',5,'f');
 pointB->work(1);
 printf("below will force to invoke\n");
 int addr = *((int *)(*(int *)(pointB)));
 printf("the addr is %p\n",addr);
 int result=0;
 int first=*((int *)(pointB)+1);
 printf("the first is %d\n",first);
 char second=*(char *)((int *)(pointB)+2);
 printf("the second is %c\n",second);
 short third=*(short *)((int *)(pointB)+3);
 printf("the third is %d\n",third);
 __asm__(
         //"mov %0,%%ecx\n\tpush $2\n\tcall *%1"::"m"(pointB),"m"(addr)

         "push $222\n\tpush %0\n\tcall *%1"::"m"(pointB),"m"(addr)
 );
 printf("after asm\n");
     //call addr;

 return 0;
 }


这个例子的主要目的是为了说明各类型的成员在类中如何分配,vtable在类中的位置以及多态的内部如何实现。

二:分析:

假设输出为这个示例代码最后被编译成a.out的可执行文件。

1,  objdump –C -t -j .data a.out|grep data

输出信息为:

08048940 l    d  .rodata        00000000

08049d00 l    d  .data  00000000

08049d08 l     O .data  00000000              p.0

08048aa4  w    O .rodata        00000008              typeinfo for empty

08048940 g     O .rodata        00000004              _fp_hw

08049d10 g     O .data  00000004              Base::wangfei

08049d04 g     O .data  00000000              .hidden __dso_handle

08049d14 g     O .data  00000004              global_data

08048a68  w    O .rodata        00000018              vtable for Derive

08048aac  w    O .rodata        00000008              typeinfo for Base

08048a80  w    O .rodata        00000014              vtable for Base

08049d00  w      .data  00000000              data_start

08049d0c g     O .data  00000004              global_text

08048ac0  w    O .rodata        00000007              typeinfo name for empty

08048acd  w    O .rodata        00000008              typeinfo name for Derive

08048ac7  w    O .rodata        00000006              typeinfo name for Base

08049d18 g       *ABS*  00000000              _edata

08048a98  w    O .rodata        0000000c              vtable for empty

08048944 g     O .rodata        00000004              _IO_stdin_used

08049d00 g       .data  00000000              __data_start

08048ab4  w    O .rodata        0000000c              typeinfo for Derive

 

红色的三行说明了一些信息:

A,类的静态成员变量不是在类里面分配的,这也正好符号了我们一般的逻辑,因为我们很可能在没有一个类实例的情况下访问它这个成员(如果是public的);

B,全局变量也是在data段里面。

C,类的函数不是在类里分配的,而是联合类名和参数变成了另外一个函数名,调用的时候传相应的类的this指针进去。

D,拥有虚函数的类会多出四个字节用于存放指向vtable的指针。

E,当发生子类指针向父类指针赋值的时候,会发生截断,对应于父类的大小的区域被分给父类,其余被截掉,但是因为vtable的指针是放在类的开始的,所以,子类的虚函数指针被赋给了父类,于是调用的时候就调用了子类的函数,这就是虚函数的内部实现!!!

 

2,  修改第56行,如果改成:

int addr = *((int *)(*(int *)(pointB))+1or 2 or 3);这样就可以分别调用第一第二个虚函数。

从这里可以看出,虚函数的位置在拥有虚函数的类的第一个位置。

3,  596163行可以看出,

类的数据的排列是按照它的申明顺序的。

4,  .rodata可以看出,vtable的内容本身是放在rodata段里面的也就是说是不可修改的。

5,  如果把43行:

char *test="hello world\n";

然后*test=”good bye”;

你会看到编译的出错信息!

原因在于”hello world\n”以及所有这些待打印出来的字符串都是放在readonlydata 段的。

要修改一个只的区域的内容当然会错了。

但是test这个变量本身你是看不到的在符号表里面,因为它会被分配在堆里面,并且是 在运行时被分配的。

但是你能看到global_text的符号,它本身是被分配在data段里面的,但它所指向的内容也是在只读段里面的。

可以通过objdump –C –d –j .rodata a.out来查看所有只读段里面的内容。

注意我上面说的段是指section不对应操作系统里面的segment

你可以用readelf –l a.out来查看具体的section  segment的对应关系。

一般rodata段都会被放在text segment.

阅读(731) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
posted on 2016-01-25 16:26  玄冬  阅读(178)  评论(0编辑  收藏  举报