您的位置:首页 > 理论基础 > 数据结构算法

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

2014-11-30 10:13 603 查看
上面一节已经探究出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)();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐