关于C++/C的内存分配
1、概述
在调试C++/C程序时,经常会遇到这样的现象,程序运行到某个函数时突然奔溃;使用New或者malloc函数分配一段内存空间时,程序运行一段时间出现奔溃现象。总之,我们经常遇到程序奔溃的问题,而这些问题有时可能也把我们自己搞得很奔溃了。但如果我们能够了解基本的堆和栈的概念,或许可以避免那些奔溃的瞬间吧。
2、内存分配
常见系统中,都是采用段式内存管理架构。这种架构将内存空间分为:代码段(text/code)、数据段(data)和BSS段(bss)。
2.1、代码段(text segment)
2.1.1、概述
代码段主要是指存放程序执行代码的一块内存区域。这部分区域所要占用的大小在程序运行前就已经确定,并且该内存区域通常属于只读。在代码段中,也有可能包含一些只读的常量变量。
2.1.2、program
用户程序存放在代码段中,并且一般该区域的属性只具有只读属性,某些架构下,也允许代码段为可写,及允许修改程序。
2.1.3、rodata
该段一般属于常量区,用于存放常量数据。rodata全称read only data,及指只读数据。所以一般将常量数据存放到该区域,但不是所有的常量都存放在rodata段,有的数据直接放在代码段中。
- 常量不一定都放在rodata区域,有的立即数会直接编码在指令里,存放在代码段(.text)中。
- 对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件中只存在一份拷贝。
- rodata是在多个进程间是共享的,这可以提高空间利用率
- 在有的嵌入式系统中,rodata放在ROM里,运行时直接读取ROM内存,无需要加载到内存空间里
由此可见,在rodata区域的数据,可以在对各进程间共享,可以大大提高空间利用率,甚至可以不占用RAM空间。另外由于rodata是在只读的内存页面中,是受保护的,任何修改其值的操作都会被及时发现,这样可以帮助提高程序的稳定性。
2.2、BSS段(bss segment)
2.2.1、概述
bss段通常用来存放程序中未初始化或初始化为0的全局变量和静态局部变量。
2.2.1、bss(Block Started by Symbol)
bss段属于静态内存分配,空间结构类似于stack。该区域的数据不会影响最终输出文件大小。由于是未初始化的变量,编译器生成输出文件时,就不需要记录初始值,只需记录分配的首末地址即可。同样对于初始值为0的变量,由于初始值已知,所以也不需要在输出文件中记录,只要在加载时,系统统一赋值0即可。
在大多数系统中,注意不是全部,在加载程序时,会把所有的bss段的变量全部清零,无需你手动去清零。但是像我之前接触过的TI的C2000系列处理器并不会帮你做上述的操作。所以,为了保证程序的可移植性,手动把这些变量初始化为0是比较好的编码习惯。
2.3、数据段(data segment)
2.3.1、概述
数据段主要用来存储程序中的初始化的且不为0的全局变量和静态变量,以及提供可供系统动态分配或者用户程序申请的内存空间。
2.3.2、静态数据(static data)
静态数据段属于静态内存分配,比较占文件空间和运行时的内存空间。
2.3.3、堆(heep)
堆的内存申请和释放都需要程序去控制,容易产生memory leak,生长方向向上。一般在C++中使用new操作符去申请内存空间,使用delete去释放相应的内存。这块区域的内存空间的释放系统不会去管,需应用程序自己去管理。所以当我们使用new去申请内存空间时,一定要注意使用delete去释放对应的内存空间,否则容易导致内存的泄漏。
2.3.4、栈(stack)
栈的内存申请和释放由系统去管理,生长方向向下,及向着内存地址减小的方向增长。主要存放函数中的局部变量,以及函数调用时,其参数也会被压入发起调用的进程栈中,并且待到函数调用结束时,函数的返回值也会被存放会栈中。由于栈的先进后出的结构特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把栈看成一个寄存、交换零时数据的内存区。
2.3.4、堆(heep)和栈(stack)的区别
对比 | 堆 | 栈 |
---|---|---|
管理方式 | 用户管理 | 系统管理 |
空间大小 | 几乎无限制 | 有限制 |
碎片产生 | 有 | 无 |
生长方向 | 向上 | 向下 |
分配方式 | 动态 | 静态和动态 |
分配效率 | 低 | 高 |