C语言面试常见问题总结--程序的5种内存存储区及示意
操作系统中的内存一般分为虚拟内存和物理内存,本文主要讨论的是虚拟内存中的内存分配方式与内存存储区的内容。
APUE中的一个典型C内存空间分布图
Linux下32位环境的用户空间内存分布情况
内存分配方式
C/C++主要有以下五种内存存储区:
1)、静态区(全局数据区):存全局变量,静态变量。程序编译时内存已分配好,并存在于程序整个运行期间,程序结束后由系统统一释放
全局变量和静态变量被分配到同一块内存中。 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(data), 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(BSS,Block Started by Symbol)
2)、栈:存放函数的参数值,局部变量,函数执行结束时会被自动释放。栈内存分配运算内置于处理器的指令集中,效率高,但是容量有限。由编译器自动分配释放。
3、堆(动态内存分配):通过new和malloc由低到高分配,由delete或free手动释放或者程序结束自动释放。动态内存的生存期人为决定,使用灵活。缺点是容易分配/释放不当容易造成内存泄漏,频繁分配/释放会产生大量内存碎片。 若程序员不释放,程序结束时可能由OS(操作系统)回收。
4、字符/文字常量区: 存放常量字符串,程序结束时由系统释放(RO)
5、代码区: 存放函数体的二进制代码(RO)
楔子
一个可执行程序文件需要在计算机硬件上运行起来,其实质就是静态的文件被加载到内存中的过程,可执行程序文件只是一个程序的载体。
一个程序被加载到内存中,这块内存首先就存在两种属性:静态分配内存和动态分配内存。
静态分配内存:是在程序编译和链接时就确定好的内存。
动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存。
基本上程序员在开始接触Linux编程时就大抵就都听过代码段、数据段等等概念,它们是各种数据存放的位置。通过objdump -h命令可以查看一个.o文件(已编译成二进制文件但未链接)的各个段:
1. 代码段(.txt)
.text段存放代码(如函数)与部分整数常量,.txt段的数据可以被执行
2. 数据段(.data)
.data用于存放初始化过的全局变量。若全局变量值为0,为了优化编译器会将它放在.bss段中
3. bss段(.bss)
.bss段被用来存放那些没有初始化或者初始化为0的全局变量。bss段只占运行时的内存空间而不占文件空间。在程序运行的整个周期内,.bss段的数据一直存在
4. 常量数据段(.rodata)
ro表read only,用于存放不可变修改的常量数据,一旦程序中对其修改将会出现段错误:
(1) 程序中的常量不一定就放在rodata中,有的立即数和指令编码放在.text中
(2) 对于字符串常量,若程序中存在重复的字符串,编译器会保证只存在一个
(3) rodata是在多个进程间共享的
(4) 有的嵌入式系统,rodata放在ROM(或者NOR FLASH)中,运行时直接读取无需加载至RAM( 哈佛和冯诺依曼,从STM32的const全局变量说起有所记录)
想要将数据放在.rodata只需要加上const属性修饰即可。
5. 栈
栈是用于存放临时变量和函数调用的。栈也是一种先进后出的数据结构,函数的递归调用正得益于栈的存在。需注意存在栈的数据只在当前函数和子函数中有效,一旦函数返回数据将会被自动释放。
6. 堆
堆的使用周期有使用者控制,程序中的内存泄漏多因程序员对堆的管理不当引起,需谨慎。
7. .comment段
在上图中还看到.comment段,它存放的是编译器版本等信息。除了.comment,还有.note、.hash等其他段,了解即可。
1.一般情况下,一个可执行二进制程序(更确切的说,在Linux操作系统下为一个进程单元,在UC/OSII中被称为任务)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。这3个部分一起组成了该可执行程序的文件。
★★可执行二进制程序 = 代码段(text)+数据段(data)+BSS段★★
2.而当程序被加载到内存单元时,则需要另外两个域:堆域和栈域。图1-1所示为可执行代码存储态和运行态的结构对照图。一个正在运行的C程序占用的内存区域分为代码段、初始化数据段、未初始化数据段(BSS)、堆、栈5个部分。
★★正在运行的C程序 = 代码段+初始化数据段(data)+未初始化数据段(BSS)+堆+栈★★
数据段 -- data
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。数据段属于静态内存分配。
bss段--bss
bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。BSS段属于静态内存分配。
stack:
栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段中 存放变量),参数以及返回值。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
heap:
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。