代码如下:
class T {
public :
T();
virtual int f(void);
private :
int d;
};
T::T(void)
{
d = 0x12345678;
}
int T::f(void)
{
return 12;
}
void x(T& t) {
t.f();
}
int main(void)
{
T t;
x(t);
return 0;
}
下面通过汇编来分析类 T 对象的内存分布。
1. 在 main 函数第一行设置断点, 观察执行 T的构造函数之前的情形:
Dump of assembler code for function main():
0x00010650 <+0>: push {r11, lr}
0x00010654 <+4>: add r11, sp, #4
0x00010658 <+8>: sub sp, sp, #8
0x0001065c <+12>: sub r3, r11, #12
0x00010660 <+16>: mov r0, r3
=> 0x00010664 <+20>: bl 0x105bc <T::T()>
0x00010668 <+24>: sub r3, r11, #12
0x0001066c <+28>: mov r0, r3
0x00010670 <+32>: bl 0x10624 <x(T&)>
0x00010674 <+36>: mov r3, #0
0x00010678 <+40>: b 0x10680 <main()+48>
0x0001067c <+44>: bl 0x10470 <__cxa_end_cleanup@plt>
0x00010680 <+48>: mov r0, r3
0x00010684 <+52>: sub sp, r11, #4
0x00010688 <+56>: pop {r11, pc}
此时的栈结构如下:
(gdb) p /x $r0
$3 = 0xbefffcc0
(gdb) p /x $sp
$4 = 0xbefffcc0
此时, r0指向 对象 t 在栈上的初始地址(对象 t大小为2字节)。
2.下面进入 T的构造函数:
(gdb) disass
Dump of assembler code for function T::T():
0x000105bc <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x000105c0 <+4>: add r11, sp, #0
0x000105c4 <+8>: sub sp, sp, #12
0x000105c8 <+12>: str r0, [r11, #-8]
0x000105cc <+16>: ldr r3, [r11, #-8]
0x000105d0 <+20>: ldr r2, [pc, #36] ; 0x105fc <T::T()+64>
0x000105d4 <+24>: str r2, [r3]
0x000105d8 <+28>: ldr r2, [r11, #-8]
0x000105dc <+32>: movw r3, #22136 ; 0x5678
0x000105e0 <+36>: movt r3, #4660 ; 0x1234
0x000105e4 <+40>: str r3, [r2, #4]
0x000105e8 <+44>: ldr r3, [r11, #-8]
0x000105ec <+48>: mov r0, r3
=> 0x000105f0 <+52>: sub sp, r11, #0
0x000105f4 <+56>: pop {r11} ; (ldr r11, [sp], #4)
0x000105f8 <+60>: bx lr
0x000105fc <+64>: andeq r0, r1, r0, lsl r7
此时,栈上的内容为:
3. 在 x()里面的情形:
Dump of assembler code for function x(T&):
=> 0x00010624 <+0>: push {r11, lr}
0x00010628 <+4>: add r11, sp, #4
0x0001062c <+8>: sub sp, sp, #8
0x00010630 <+12>: str r0, [r11, #-8]
0x00010634 <+16>: ldr r3, [r11, #-8]
0x00010638 <+20>: ldr r3, [r3]
0x0001063c <+24>: ldr r3, [r3]
0x00010640 <+28>: ldr r0, [r11, #-8]
0x00010644 <+32>: blx r3
0x00010648 <+36>: sub sp, r11, #4
0x0001064c <+40>: pop {r11, pc}
(gdb) p /x $sp
$20 = 0xbefffcc0
(gdb) p /x $r0
$21 = 0xbefffcc0
(gdb) p /x $r11
$22 = 0xbefffccc
Dump of assembler code for function x(T&):
0x00010624 <+0>: push {r11, lr}
0x00010628 <+4>: add r11, sp, #4
0x0001062c <+8>: sub sp, sp, #8
0x00010630 <+12>: str r0, [r11, #-8]
0x00010634 <+16>: ldr r3, [r11, #-8]
0x00010638 <+20>: ldr r3, [r3]
0x0001063c <+24>: ldr r3, [r3]
0x00010640 <+28>: ldr r0, [r11, #-8]
=> 0x00010644 <+32>: blx r3
0x00010648 <+36>: sub sp, r11, #4
0x0001064c <+40>: pop {r11, pc}
End of assembler dump.
(gdb) p /x $r3
$25 = 0x10600
(gdb) x $r3
0x10600 <T::f()>: 0xe52db004
(gdb) p /x $r0
$26 = 0xbefffcc0
(gdb) x $r0
0xbefffcc0: 0x00010710
(gdb) x 0x00010710
0x10710 <_ZTV1T+8>: 0x00010600
此时,栈以及调用关系如下:
如前所说, T 占了8个字节,一个4字节是成员变量的,另一个就是vtbl (虚表)指针所占用。这里虚表指向 T::f()的地址。
虚表信息也可以从 gdb观察到:
(gdb) p t
$28 = {_vptr.T = 0x10710 <vtable for T+8>, d = 305419896}
(gdb) info vtbl t
vtable for 'T' @ 0x10710 (subobject @ 0xbefffcc0):
[0]: 0x10600 <T::f()>
(gdb) p &t
$29 = (T *) 0xbefffcc0
reference:
1. http://blog.httrack.com/blog/2014/05/09/a-basic-glance-at-the-virtual-table/
2.http://www.lrdev.com/lr/c/virtual.html
3.https://cloudfundoo.wordpress.com/2012/04/27/deep-c-understanding-c-object-layout/