前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类

《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类

作者头像
血狼debugeeker
发布2018-09-20 14:40:17
8120
发布2018-09-20 14:40:17
举报
文章被收录于专栏:debugeeker的专栏debugeeker的专栏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344519

上面一节已经探究出this指针的辨别,由this指针就可以看到类的内容。在这里,就由this指针来看一下类的成员变量是如何排列。

先看一个例子

代码语言:javascript
复制
  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	 }

汇编代码:

代码语言:javascript
复制
(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这三个值。

代码语言:javascript
复制
(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函数的汇编:

代码语言:javascript
复制
(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指针作为成员函数的第一个参数。这应该也是为什么类成员函数指针声明要指定类名的原因。修改一下例子的源代码来验证一下这个结论

代码语言:javascript
复制
  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函数的汇编:

代码语言:javascript
复制
(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)();

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2014年11月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档