C++ 常见崩溃问题分析_c++ bus error

在介绍详细介绍进程空间内存布局之前,我们首先看一下 Windows 的资源管理器进程的内存布局视图,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图(一)Windows资源管理器内存分布图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图(二)Windows资源管理器内存地址空间分布图

从上图可以看出,进程内存地址空间被划分为8块(Managed Heap是另一种内存堆),并且各块内存不是聚集在一起形成连续内存块,而是按需加载使用内存;他们的详细情况如下:

内存块英文名 中文名 详细说明
Image 映像内存 EXE、DLL等加载到这里
Mapped File 内存映射文件 共享内存,用于进程间通讯
Shareable 可共享内存
Heap (Managed Heap) 内存堆 堆内存,new/new[]/malloc等都在堆空间分配,默认为1 MB;Managed Heap 供CLR使用的堆
Stack 堆栈 栈内存,用做函数参数、局部变量的存储空间,默认为1 MB
Private Data 私有数据
Page Table 内存页表 内存分配页表
Free 自由内存 可用的内存空间

由于编译器在后台做了大量的内存管理自动化工作,因此程序设计过程中主要关注的内存区域类型有:Stack、Heap、Free(Free Virtual Address Space),下面我们对这几种做一个简要介绍:

Stack 是一块固定大小的连续内存,受运行时管理,无需用户自行分配和回收;当函数调用嵌套层次非常深时会产生 Stack overflow(堆栈溢出)错误,如递归调用、循环调用、消息循环、大对象参数、大对象局部变量等都容易触发堆栈溢出;

Heap 主要用于管理小内存块,是一个内存管理单元,默认为1MB,可动态增长;每一个应用程序默认有一个 Heap,用户也可以创建自己的 Heap,new/delete, malloc/free 都是从堆中直接分配内存块;

Free(Free Virtual Address Space)即进程空间中的整个可用地址空间,它会以两种方式被使用,一种是Heap 自动分配和回收,一种是直接使用VirtualAlloc*/VirtualFree* 分配和回收;用户对它的直接使用是用于分配连续大块内存,分配和释放的速度比使用 Heap 更快;

3.3、数据结构视图

内存始终都还是内存,所不同的是我们解读内存的方式不同;从代码视野来看内存中的数据结构,它就是对一块连续内存的专有解读;对任何一个内存地址,我们可以用数据结构A视图来解读,亦可以用数据结构B视图来解读,使用正确的数据结构视图读到正确的数据,使用错误的数据结构视图我们读到错误的数据;为了简明扼要的说明这个问题,我们来个案例:

char szSentence[] = “this is a example”; int * nValue = (int *)szSentence;           ----> *nValue = 1310540

记住这一点非常重要,C / C++ 程序设计中的很多技术法门都出自这;例如基本类型转换、指针对象转换、面向对象的多态、改写只读对象等都体现为连续内存块的解读视图变化;

由于操作系统已经接管了物理内存的使用,并且提供了透明的访问机制,对内存的使用更直接体现为对操作系统提供的进程地址空间的分配和回收;

在实际的编程实践中,程序员需要把整块空间再细分为8位、16位、32位、64位、8位连续块等数据空间,这里还涉及到两个概念:字节对齐和字节序列(又名端序,有大端小端之说),透彻理解编译器的对齐规则和处理所支持的字节序列,对于正确理解内存中的数据很关键。

3.3.1、字节序列

字节序列对于网络编程的同学尤其熟悉,因为需要把数据包在本机字节序列和网络字节序列(大端序列)来回转换,部分经常使用printf和基于偏移量访问内存的同学也会遇到字节序列带来的烦恼;

字节序列简单的讲是对大于一个字节的数据在内存中如何存放的问题,比如32位整数需要使用4个字节,这个四个字节该如何放置?按照二进制位切割为四个字节吗?下面我们详细介绍一下字节序列的两种定义:

端序 第一字节 中间字节 最末字节 备注
大端(Big Endian) 最高位字节 …… 最低位字节 类似于正常书写数字表示
小端(Little Endian) 最低位字节 …… 最高位字节 类似数学计算法则,反序列

端序的内存案例:

端序 内存案例(0x44332211) 处理器家族
大端(Big Endian) 0x44 0x33 0x22 0x11 SUN SPARC/IBM PowerPC
小端(Little Endian) 0x11 0x22 0x33 0x44 Intel 80x86 系列

下面我们来看一个实践中产生的和端序想关联的问题,案例来自 Vimer的程序世界

int64_t a = 1; int b = 2; printf(“%d, %d**\n**”, a, b);  ====> 1, 0

为什么会这样?有同事对该问题做了精辟的注解,为了尊重作者版权,故截图分享,请看下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

【注】这个Bug依赖于编译器实现,可能在某些编译器上不会重现。

3.3.2、字节对齐

字节对齐涉及内存分配的问题,具体涉及到结构、联合类型、类成员数据的对齐分配,编译器根据不同的对齐规则分配不同的内存空间。

平台编译器 支持对齐规则 修改方法(四字节对齐)
Microsoft C/C++ 1/2/4/8/16,default: 8 #pragma pack(4) __declspec(align(4))
GNU GCC 4.6 1/2/4/8/16,default: by ABI __attribute__(packed

掌握对齐规则后,我们就可以在使用标量类型时、设计结构、联合、类类型时合理选择类型,既可以合理使用内存空间,又可以提高程序性能;下面我们看一个来自实践中的案例:

#pragma pack(1) struct tagPROJECTPROPERTY {     char szBusiness[SCHEMA_NAME_MAX_LEN];       // 64 byte     char szTeamName[SCHEMA_NAME_MAX_LEN];       // 64 byte     char szLanguage[SCHEMA_NAME_MAX_LEN];       // 64 byte     char szExtension[SCHEMA_FILE_EXT_LEN];      // 64 byte     char szProjectGUID[SCHEMA_UUID_MAX_LEN];    // 7 byte     char szProjectName[SCHEMA_NAME_MAX_LEN];    // 64 byte     uint32_t dwEntryTotal;                        // 4 byte     }; #pragma pop

类型定义 对齐(1 B) 对齐(2 B) 对齐(4 B) 默认(8 B)
sizeof(tagPROJECTPROPERTY) 331 BYTE 332 BYTE 332 BYTE 336 BYTE

从上面我们可以看出,在默认对齐规则下,单个实例会浪费5个字节的内存,如果1万实例则会浪费 48 K内存,如果再加上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值