gcc下c++的对象模型 (1)

所有示例代码在如下环境中执行

ubuntu 16.04.4 (64位)
gcc version 5.4.0 开启std11
gdb version 7.11.1

1. 空类的大小

定义一个空类A,实例化对象a,b。分别查看a的地址和b的地址。代码如下:

#include <iostream>

class A { };
int main() {
    A a;
    A b;
    std::cout << sizeof(A) << std::endl;
    std::cout << &a << " " << &b <<std::endl;
    return 0;
}
//示例1.1

输出结果:

1
0x7fff71124726 0x7fff71124727

类的实例化需要在内存中分配地址,编译器为了确保一个空类也能够实例化并且是独一无二的
所以在一个空类中加入了一个字节。
这个结果仅仅在当前示例和环境中有效,并不是四海皆准,c++标准中并没有规定加入的隐含字节
到底是多大。当然,1字节是保证空类能够实例化的最小消耗。

2. 拥有virtual函数的类A

修改上面的代码如下:

#include <iostream>

class A
{
public:
    virtual void fun() {
        std::cout<<"HELLO"<<std::endl;
    }
};
int main() {
    A a;
    A b;
    std::cout<<sizeof(A)<<std::endl;
    std::cout<<&a<<" "<<&b<<std::endl;
    return 0;
}
//示例2.1

结果:

8
0x7ffc67d214c0 0x7ffc67d214d0

大小为8,从示例1.1中可以知道两个变量地址的差值代表了实际内存分配的大小,
这里0x7ffc67d214d0与0x7ffc67d214c0的差值为16,与sizeof的结果不同。那么多出来的8字节是什么?
如果继续增加虚函数,sizeof的结果和变量地址的差值会增加吗?
继续修改示例2.1的代码,增加虚函数funa和funb,如下:

#include <iostream>

class A
{
public:
    virtual void fun() {
        std::cout<<"HELLO"<<std::endl;
    }
    virtual void funa() {
        std::cout<<"HELLO funa"<<std::endl;
    }
    virtual void funb() {
        std::cout<<"HELLO funb"<<std::endl;
    }
};
int main() {
    A a;
    A b;
    std::cout<<sizeof(A)<<std::endl;
    std::cout<<&a<<" "<<&b<<std::endl;
    return 0;
}
//示例2.2

运行结果如下:

8
0x7ffd5a3d4040 0x7ffd5a3d4050

结果没有变化,大小为8,差值仍然为16,现在需要做的就是确认这”多出来的“8字节内容,
以及fun funa funb到底在哪里。

3 含有virual的类A的对象模型

仍然以示例2.2为例,在本节内容中,要引入一个强大的工具GDB
执行如下命令生成a.out:

icekylin@icekylin:~/clion_projects/untitled$ g++ -g main.cpp 
icekylin@icekylin:~/clion_projects/untitled$ ls
a.out  cmake-build-debug  CMakeLists.txt  main.cpp
icekylin@icekylin:~/clion_projects/untitled$

使用gdb调试a.out,并在代码的20打入断点,使用r命令运行程序:

icekylin@icekylin:~/clion_projects/untitled$ gdb a.out
(gdb) b main.cpp:20
Breakpoint 1 at 0x4009a5: file main.cpp, line 20.
(gdb) r
Starting program: /home/icekylin/clion_projects/untitled/a.out 

Breakpoint 1, main () at main.cpp:20
20      std::cout<<sizeof(A)<<std::endl;

程序执行到这个位置后,先查看对象a,b的地址。

(gdb) p &a
$1 = (A *) 0x7fffffffdd50
(gdb) p &b
$2 = (A *) 0x7fffffffdd60
(gdb) p sizeof(A)
$3 = 8

先查看下对象a,b都包含了什么内容:

(gdb) x /16xw 0x7fffffffdd50
0x7fffffffdd50: >0x00400b80<    0x00000000  0x00400880  0x00000000
0x7fffffffdd60: >0x00400b80<    0x00000000  0x0d861c00  0x9c37e70f
0x7fffffffdd70: 0x00400ad0  0x00000000  0xf76ac830  0x00007fff
0x7fffffffdd80: 0x00000000  0x00000000  0xffffde58  0x00007fff

x命令是gdb中用来打印内存的命令,具体内容可问闷得儿(baidu)或狗剩(google)。
这一坨是什么东西?都是16进制,看不出什么含义,仅知道对象a和b都指向一个相同的内容(0x00400b80)
不同pa,在linux中还有另外一个强大的命令 pmap,用pmap可以查看指定进程的地址空间分布。

icekylin@icekylin:~/clion_projects/untitled$ pidof a.out
31272
icekylin@icekylin:~/clion_projects/untitled$ pmap -d 31272
31272:   /home/icekylin/clion_projects/untitled/a.out
Address           Kbytes Mode  Offset           Device    Mapping
(1) 0000000000400000       4 r-x-- 0000000000000000 008:00012 a.out
(2) 0000000000600000       4 r---- 0000000000000000 008:00012 a.out
(3) 0000000000601000       4 rw--- 0000000000001000 008:00012 a.out
0000000000602000     200 rw--- 0000000000000000 000:00000   [ anon ]
00007ffff716d000      88 r-x-- 0000000000000000 008:00012 libgcc_s.so.1
00007ffff7183000    2044 ----- 0000000000016000 008:00012 libgcc_s.so.1
00007ffff7382000       4 rw--- 0000000000015000 008:00012 libgcc_s.so.1
00007ffff7383000    1056 r-x-- 0000000000000000 008:00012 libm-2.23.so
00007ffff748b000    2044 ----- 0000000000108000 008:00012 libm-2.23.so
00007ffff768a000       4 r---- 0000000000107000 008:00012 libm-2.23.so
00007ffff768b000       4 rw--- 0000000000108000 008:00012 libm-2.23.so
00007ffff768c000    1788 r-x-- 0000000000000000 008:00012 libc-2.23.so
00007ffff784b000    2048 ----- 00000000001bf000 008:00012 libc-2.23.so
00007ffff7a4b000      16 r---- 00000000001bf000 008:00012 libc-2.23.so
00007ffff7a4f000       8 rw--- 00000000001c3000 008:00012 libc-2.23.so
00007ffff7a51000      16 rw--- 0000000000000000 000:00000   [ anon ]
00007ffff7a55000    1480 r-x-- 0000000000000000 008:00012 libstdc++.so.6.0.21
00007ffff7bc7000    2048 ----- 0000000000172000 008:00012 libstdc++.so.6.0.21
00007ffff7dc7000      40 r---- 0000000000172000 008:00012 libstdc++.so.6.0.21
00007ffff7dd1000       8 rw--- 000000000017c000 008:00012 libstdc++.so.6.0.21
00007ffff7dd3000      16 rw--- 0000000000000000 000:00000   [ anon ]
00007ffff7dd7000     152 r-x-- 0000000000000000 008:00012 ld-2.23.so
00007ffff7fd5000      20 rw--- 0000000000000000 000:00000   [ anon ]
00007ffff7ff6000       8 rw--- 0000000000000000 000:00000   [ anon ]
00007ffff7ff8000       8 r---- 0000000000000000 000:00000   [ anon ]
00007ffff7ffa000       8 r-x-- 0000000000000000 000:00000   [ anon ]
00007ffff7ffc000       4 r---- 0000000000025000 008:00012 ld-2.23.so
00007ffff7ffd000       4 rw--- 0000000000026000 008:00012 ld-2.23.so
00007ffff7ffe000       4 rw--- 0000000000000000 000:00000   [ anon ]
00007ffffffde000     132 rw--- 0000000000000000 000:00000   [ stack ]
ffffffffff600000       4 r-x-- 0000000000000000 000:00000   [ anon ]
mapped: 13268K    writeable/private: 428K    shared: 0K

本文中只关心在上述结果标记的序号内容。

  • (1) a.out的代码段
  • (2) a.out的数据段
  • (3) a.out的堆

从序号1和2可以看出0x00400b80位于进程的代码段上,“多出来的”0x00400880也位于代码段。
既然是代码就好办了,回到gdb的窗口,使用disassemble查看下0x00400880到底是什么?

(gdb) disassemble 0x00400880
Dump of assembler code for function _start:
   0x0000000000400880 <+0>: xor    %ebp,%ebp
   0x0000000000400882 <+2>: mov    %rdx,%r9
   0x0000000000400885 <+5>: pop    %rsi
   0x0000000000400886 <+6>: mov    %rsp,%rdx
   0x0000000000400889 <+9>: and    $0xfffffffffffffff0,%rsp
   0x000000000040088d <+13>:    push   %rax
   0x000000000040088e <+14>:    push   %rsp
   0x000000000040088f <+15>:    mov    $0x400b40,%r8
   0x0000000000400896 <+22>:    mov    $0x400ad0,%rcx
   0x000000000040089d <+29>:    mov    $0x400976,%rdi
   0x00000000004008a4 <+36>:    callq  0x4007f0 <__libc_start_main@plt>
   0x00000000004008a9 <+41>:    hlt    
End of assembler dump.

原来认为多出来的内存内容居然是_start这货,(关于_start请自行闷得儿)。
现在在确认下0x00400b80地址到底是什么:

(gdb) disassemble 0x00400b80
Dump of assembler code for function _ZTV1A:
   0x0000000000400b70 <+0>: add    %al,(%rax)
   0x0000000000400b72 <+2>: add    %al,(%rax)
   0x0000000000400b74 <+4>: add    %al,(%rax)
   0x0000000000400b76 <+6>: add    %al,(%rax)
   0x0000000000400b78 <+8>: cwtl   
   0x0000000000400b79 <+9>: or     0x0(%rax),%eax
   0x0000000000400b7c <+12>:    add    %al,(%rax)
   0x0000000000400b7e <+14>:    add    %al,(%rax)
   0x0000000000400b80 <+16>:    xor    %cl,(%rdx)
   0x0000000000400b82 <+18>:    add    %al,(%rax)
   0x0000000000400b85 <+21>:    add    %al,(%rax)
   0x0000000000400b87 <+23>:    add    %bl,0x40(%rdx,%rcx,1)
   0x0000000000400b8b <+27>:    add    %al,(%rax)
   0x0000000000400b8d <+29>:    add    %al,(%rax)
   0x0000000000400b8f <+31>:    add    %cl,0x400a(%rax)
   0x0000000000400b95 <+37>:    add    %al,(%rax)
   0x0000000000400b97 <+39>:    add    %dl,0x6010(%rax)
End of assembler dump.

恭喜你终于找到了类A,不必犹豫,直接x命令查看这个地址内容吧。

(gdb) x /16xw 0x00400b80
0x400b80 <_ZTV1A+16>:   0x00400a30  0x00000000  0x00400a5c  0x00000000
0x400b90 <_ZTV1A+32>:   0x00400a88  0x00000000  0x00601090  0x00000000
0x400ba0 <_ZTI1A+8>:    0x00400ba8  0x00000000  0x00004131  0x3b031b01
0x400bb0:   0x00000060  0x0000000b  0xfffffc24  0x000000ac

见到代码段的内容,毫不犹豫直接disassemble:

(gdb) disassemble 0x00400a30
Dump of assembler code for function A::fun():
(gdb) disassemble 0x00400a5c
Dump of assembler code for function A::funa():
(gdb) disassemble 0x00400a88
Dump of assembler code for function A::funb():

按照地址的排序,顺序放着fun,funa,funb,在代码段中找到了这三个函数。
到这里笔者相信根据以上的信息足够画出类A的内存模型图了。

4. 类A的对象模型图

o_class_A.png

转载于:https://www.cnblogs.com/eskylin/p/6491256.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值