上面一节已经探究出this指针的辨别,由this指针就可以看到类的内容。在这里,就由this指针来看一下类的成员变量是如何排列。
先看一个例子
1 #include <stdio.h> 2 class xuzhina_dump_c06_s2 3 { 4 private: 5 short m_c; 6 char m_d; 7 int m_e; 8 9 public: 10 xuzhina_dump_c06_s2( int a, int b ) 11 { 12 m_c = (short)(a + b); 13 m_d = 'd'; 14 m_e = a - b; 15 } 16 void print( ) 17 { 18 printf( "member %d, %c, %d\n", m_c, m_d, m_e ); 19 } 20 }; 21 22 int main() 23 { 24 xuzhina_dump_c06_s2 test( 2, 3 ); 25 test.print(); 26 return 0; 27 }
汇编代码:
(gdb) disassemble main Dump of assembler code for function main: 0x08048570 <+0>: push %ebp 0x08048571 <+1>: mov %esp,%ebp 0x08048573 <+3>: and $0xfffffff0,%esp 0x08048576 <+6>: sub $0x20,%esp 0x08048579 <+9>: movl $0x3,0x8(%esp) 0x08048581 <+17>: movl $0x2,0x4(%esp) 0x08048589 <+25>: lea 0x18(%esp),%eax 0x0804858d <+29>: mov %eax,(%esp) 0x08048590 <+32>: call 0x80485b2 <_ZN19xuzhina_dump_c06_s2C2Eii> 0x08048595 <+37>: lea 0x18(%esp),%eax 0x08048599 <+41>: mov %eax,(%esp) 0x0804859c <+44>: call 0x80485de <_ZN19xuzhina_dump_c06_s25printEv> 0x080485a1 <+49>: mov $0x0,%eax 0x080485a6 <+54>: jmp 0x80485b0 <main+64> 0x080485a8 <+56>: mov %eax,(%esp) 0x080485ab <+59>: call 0x8048460 <_Unwind_Resume@plt> 0x080485b0 <+64>: leave 0x080485b1 <+65>: ret End of assembler dump.
由上面代码可得知,test在构造完成后,它三个成员的值分别是5, ‘d’,-1(0xffffffff), 且由上一节得知,在调用类的成员函数时,this指针作为第一个参数放入栈里。
所以,可以在指令地址0x0804859c打断点看看,当函数运行到断点时,ecx所指向内存是怎样存放着5,’d’,-1这三个值。
(gdb) tbreak *0x0804859c Temporary breakpoint 1 at 0x804859c (gdb) r Starting program: /home/buckxu/work/6/2/xuzhina_dump_c6_s2 Temporary breakpoint 1, 0x0804859c in main () (gdb) x /8x $esp+0x18 0xbffff478: 0x08640005 0xffffffff 0x08048620 0x00000000 0xbffff488: 0x00000000 0x4362f635 0x00000001 0xbffff524 (gdb) x /c 0xbffff47a 0xbffff47a: 100 'd'
可以看到this指针指向内存的内容按照由低到高的顺序分别是5, d, -1.由于m_c, m_d分别是char,short类型,所以,它们就挤在同一个32-bit单元里,进行内存对齐。
由此可知,类的成员变量排列和结构体没什么区别,只是在调用成员函数时,this指针会作为成员函数第一个参数放入栈中。即定位coredump问题,可以看看在调用类成员函数时看一下它的第一个参数,找到this指针,然后根据this指针查看类每个成员变量的值。
同时,上面可以看到,调用类成员函数和调用普通函数的区别就是,在每次调用类成员函数时,都会把this指针作为第一个参数传递。这个区别应该就是类成员函数可以直接调用类成员变量的原因,因为可以把第一个参数作为基址,来访问变量。
可以看一下类xuzhina_dump_c06_s2的print函数的汇编:
(gdb) shell c++filt _ZN19xuzhina_dump_c06_s25printEv xuzhina_dump_c06_s2::print() (gdb) disassemble _ZN19xuzhina_dump_c06_s25printEv Dump of assembler code for function _ZN19xuzhina_dump_c06_s25printEv: 0x080485de <+0>: push %ebp 0x080485df <+1>: mov %esp,%ebp 0x080485e1 <+3>: sub $0x18,%esp 0x080485e4 <+6>: mov 0x8(%ebp),%eax //this指针 0x080485e7 <+9>: mov 0x4(%eax),%ecx //m_e 0x080485ea <+12>: mov 0x8(%ebp),%eax //this指针 0x080485ed <+15>: movzbl 0x2(%eax),%eax // m_d 0x080485f1 <+19>: movsbl %al,%edx 0x080485f4 <+22>: mov 0x8(%ebp),%eax //this指针 0x080485f7 <+25>: movzwl (%eax),%eax //m_c 0x080485fa <+28>: cwtl 0x080485fb <+29>: mov %ecx,0xc(%esp) 0x080485ff <+33>: mov %edx,0x8(%esp) 0x08048603 <+37>: mov %eax,0x4(%esp) 0x08048607 <+41>: movl $0x80486b4,(%esp) 0x0804860e <+48>: call 0x8048440 <printf@plt> 0x08048613 <+53>: leave 0x08048614 <+54>: ret End of assembler dump.
从而可以看到,类成员函数和普通的区别确实在于会把this指针作为成员函数的第一个参数。这应该也是为什么类成员函数指针声明要指定类名的原因。修改一下例子的源代码来验证一下这个结论
1 #include <stdio.h> 2 class xuzhina_dump_c06_s2 3 { 4 private: 5 short m_c; 6 char m_d; 7 int m_e; 8 9 public: 10 xuzhina_dump_c06_s2( int a, int b ) 11 { 12 m_c = (short)(a + b); 13 m_d = 'd'; 14 m_e = a - b; 15 } 16 void print( ) 17 { 18 printf( "member %d, %c, %d\n", m_c, m_d, m_e ); 19 } 20 }; 21 typedef void (xuzhina_dump_c06_s2::*func_ptr)(); 22 int main() 23 { 24 xuzhina_dump_c06_s2 test( 2, 3 ); 25 func_ptr clsFuncPtr = &xuzhina_dump_c06_s2::print; 26 (test.*clsFuncPtr)(); 27 return 0; 28 }
看一下main函数的汇编:
(gdb) disassemble main Dump of assembler code for function main: 0x08048570 <+0>: push %ebp 0x08048571 <+1>: mov %esp,%ebp 0x08048573 <+3>: and $0xfffffff0,%esp 0x08048576 <+6>: sub $0x20,%esp 0x08048579 <+9>: movl $0x3,0x8(%esp) 0x08048581 <+17>: movl $0x2,0x4(%esp) 0x08048589 <+25>: lea 0x18(%esp),%eax 0x0804858d <+29>: mov %eax,(%esp) 0x08048590 <+32>: call 0x80485ee <_ZN19xuzhina_dump_c06_s2C2Eii> 0x08048595 <+37>: movl $0x804861a,0x10(%esp) 0x0804859d <+45>: movl $0x0,0x14(%esp) 0x080485a5 <+53>: mov 0x10(%esp),%eax 0x080485a9 <+57>: and $0x1,%eax 0x080485ac <+60>: test %eax,%eax 0x080485ae <+62>: jne 0x80485b6 <main+70> 0x080485b0 <+64>: mov 0x10(%esp),%eax 0x080485b4 <+68>: jmp 0x80485cd <main+93> 0x080485b6 <+70>: mov 0x14(%esp),%eax 0x080485ba <+74>: lea 0x18(%esp),%edx 0x080485be <+78>: add %edx,%eax 0x080485c0 <+80>: mov (%eax),%edx 0x080485c2 <+82>: mov 0x10(%esp),%eax 0x080485c6 <+86>: sub $0x1,%eax 0x080485c9 <+89>: add %edx,%eax 0x080485cb <+91>: mov (%eax),%eax 0x080485cd <+93>: mov 0x14(%esp),%edx 0x080485d1 <+97>: lea 0x18(%esp),%ecx 0x080485d5 <+101>: add %ecx,%edx 0x080485d7 <+103>: mov %edx,(%esp) 0x080485da <+106>: call *%eax 0x080485dc <+108>: mov $0x0,%eax 0x080485e1 <+113>: jmp 0x80485eb <main+123> 0x080485e3 <+115>: mov %eax,(%esp) 0x080485e6 <+118>: call 0x8048460 <_Unwind_Resume@plt> 0x080485eb <+123>: leave 0x080485ec <+124>: ret End of assembler dump.
可见,类成员函数指针的使用和类成员函数一样,都会把this指针作为成员函数的第一个参数。这也是为什么调用类成员函数时要指定对象或对象指针。如这个例子的
(test.*clsFuncPtr)();
《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类
原文地址:http://blog.csdn.net/xuzhina/article/details/41620563