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

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的对象模型图

gcc下c++的对象模型 (1)的更多相关文章

  1. MinGW GCC下sleep()函数问题

    在MinGW GCC下编译带sleep()函数的测试程序,不管是包含了unistd.h头文件,还是stdio.h.stdlib.h头文件,就是找不到该函数的定义!在linux下,sleep()函数的头 ...

  2. GCC下32位与64位机器类型变量所占字节数

    GCC下32位与64位机器类型变量所占字节数 在C语言中,编译器一般根据自身硬件针对类型变量来选择合适的字节大小,下面列举一下在GCC编译器下32位机器与64位机器各个类型变量所占字节数目: C语言 ...

  3. C++对象模型(虽然在GCC下很大的不同,但是先收藏)

    C++对象模型 何为C++对象模型? 部分: 1.        语言中直接支持面向对象程序设计的部分 2.        对于各种支持的底层实现机制 语言中直接支持面向对象程序设计的部分,如构造函数 ...

  4. Windows+GCC下内存对齐的常见问题

    结构/类对齐的声明方式 gcc和windows对于modifier/attribute的支持其实是差不多的.比如在gcc的例子中,内存对齐要写成: class X { //... } __attrib ...

  5. 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局

    继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局. 一.多重继承 先看几个类的定义: 01 ...

  6. gcc下inline的一个问题

    今天发现一个问题,与inline有关,也与编译时候是不是优化有关. 大概问题可以用下面的代码来描述: 先写一个libtest1,代码如下 libtest1.h #ifndef LIBTEST_H #d ...

  7. Linux GCC下strstr的实现以及一个简单的Kmp算法的接口

    今天做了一道题,要用判断一个字符串是否是另一个字符串的子串,于是查了一下strstr的实现. 代码如下: char *strstr(const char*s1,const char*s2) { con ...

  8. C++对象模型的那些事儿之二:对象模型(下)

    前言 上一篇博客C++对象模型的那些事儿之一为大家讲解了C++对象模型的一些基本知识,可是C++的继承,多态这些特性如何体现在对象模型上呢?单继承.多重继承和虚继承后内存布局上又有哪些变化呢?多态真正 ...

  9. gcc和MinGW的异同(在cygwin/gcc做的东西可以无缝的用在linux下,没有任何问题,是在windows下开发linux程序的一个很好的选择)

    cygwin/gcc和MinGW都是gcc在windows下的编译环境,但是它们有什么区别,在实际工作中如何选择这两种编译器. cygwin/gcc完全可以和在linux下的gcc化做等号,这个可以从 ...

随机推荐

  1. KB奇遇记(9):艰难的上线

    经历了非常多的磨难,系统也“如约“在2017年01月01日勉强上线了.尽管我认为它还不到上线的程度,条件不具备,但上头的指令下来和计划便是在这一天.整个上线过程从2016年3月8号开始到上线日,扣除中 ...

  2. MongoDB的账户与权限管理及在Python与Java中的登陆

    本文主要介绍了MongoDB的账户新建,权限管理(简单的),以及在Python,Java和默认客户端中的登陆. 默认的MongoDB是没有账户权限管理的,也就是说,不需要密码即可登陆,即可拥有读写的权 ...

  3. ArcGIS API for JavaScript 4.2学习笔记[1] 显示地图

    ArcGIS API for JavaScript 4.2直接从官网的Sample中学习,API Reference也是从官网翻译理解过来,鉴于网上截稿前还没有人发布过4.2的学习笔记,我就试试吧. ...

  4. (@WhiteTaken)设计模式学习——简单工厂

    最近工作比较忙,所以没有怎么写博客,这几天将集中学习一下(厉风行)讲解的设计模式的相关知识,并对主要的代码进行介绍. 言归正传,接下来介绍最简单也是最基础的简单工厂设计模式. 什么是简单工厂? 简单工 ...

  5. Java之英格玛简单实现以及加密验证码的应用

    最近看了一部电影<模仿游戏>,<模仿游戏>中艾伦·图灵破译英格玛让我对英格玛产生了好奇,于是就开始翻阅资料对其进行研究,但是毕竟智慧有限,所以我这里用Java实现一个简单的英格 ...

  6. static的加载先后顺序

    1.静态变量的声明和赋值是分开的,静态变量会先被声明,赋值操做被放在了静态代码块中. 2.静态变量的赋值和静态代码块的执行顺序和代码的先后书写顺序相关. 3.静态代码块优先执行,其次构造方法,最后普通 ...

  7. 2月22日 《从Paxos到Zookeeper 分布式一致性原理与实践》读后感

    zk的特点: 分布式一致性的解决方案,包括:顺序一致性,原子性,单一视图,可靠性,实时性 zk的基本概念: 集群角色:not Master/Slave,is Leader/Follower/Obser ...

  8. 2017-2-19 C#基础 基本数据类型的转换,转义字符,常量

    1.基本数据类型的转换分两种:自动转换(隐式转换)和强制转换(显式转换).自动转换是从只类型转换到引用类型.强制转换是最长用的,是从引用类型转换到值类型或者从浮点型转换到整形.强制转换主要有三种形式: ...

  9. 301、404、200、304、500HTTP状态

    一些常见的状态码为: 200 - 服务器成功返回网页 404 - 请求的网页不存在 503 - 服务器超时 下面提供 HTTP 状态码的完整列表.点击链接可了解详情.您也可以访问 HTTP 状态码上的 ...

  10. 面对考试毫无畏惧的SSH

    [Struts+Spring+Hibernate] 新建一个项目 把SSH jar包(包含mysql.oracle.jackson等包在里面) 把web.xml复制到WebContent\WEB-IN ...