原文:
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))+1(or 2 or 3));这样就可以分别调用第一第二个虚函数。
从这里可以看出,虚函数的位置在拥有虚函数的类的第一个位置。
3, 从59,61,63行可以看出,
类的数据的排列是按照它的申明顺序的。
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.
阅读(1234) | 评论(0) | 转发(0) |