版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344519
上面一节已经探究出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)();