程序在内存中的布局

时间:2021-12-19 14:55:32

程序在运行时,由操作系统将可执行文件载入到计算机的内存中,成为一个进程(process)。程序进程被创建时,系统就会为其分配内存空间。程序在内存中的布局由5个段(segment)组成,如下图所示:

程序在内存中的布局

1、代码段

代码段(code segment)存放程序执行的机器指令。通常情况下,代码段是可共享的,使其可共享的目的是,对于频繁被执行的程序,只需要在内存中有一份副本即可。代码段是只读的,使其只读的原因是防止一个程序意外地修改它的指令。
C程序的代码部分全部放在代码段。程序运行时由操作系统从程序映像中取出代码段,布局在程序的内存地址最低的区域,然后跳转到代码段的main函数开始运行程序,程序结束后由操作系统收回这段内存区域。

2、已初始化数据段

已初始化数据段(data segment)用来存放C程序中的所有已赋值的全局变量和静态变量、对象,也包括字符串、数组等常量,但基本类型的常量不包含在其中,因为这些常量被编译成指令的一部分存放于代码段。函数中创建的字符串常量也是保存在已初始化数据段中,因此,这些字符串常量即使在函数返回之后,依然可以被引用,一直到程序结束。
程序运行时由操作系统从程序映像中取出data段,布局在程序内存地址较低的区域。程序结束后由操作系统回收这段内存区域。
显然,data段的存储单元与程序代码相同的生命周期,它们的初始值实际在编译的时候已经确定了,即使程序没有运行,这些存储单元的初始值也固定下来了,当程序开始运行时,这些单元是没有初始化的动作。
在程序运行中,data段的存储单元数据会一直保持到改变为止,或保持到程序结束为止。

3、未初始化数据段

未初始化数据段(bss segment)用来储存C程序中所有为赋值的全局和静态变量。在程序映像中没有储存bss段,只有它们的空间大小信息;程序运行前由操作系统根据这个大小信息分配bss段,且数据值全部初始化为零,布局在与data段相邻的区域。程序结束后有操作系统回收这段内存区域。
显然,bss段的储存单元也有与程序代码相同的生命周期,但与data段不同的是如果城程序没有运行,bss段的储存空间是不存在的,因而也就不会有初始值。在程序运行前,这些储存单元会被初始化为零。此后,bss段的储存单元的性质完全与data段相同。

4、栈(stack)

栈(stack)用来存放C程序中所有的局部的非静态型变量、临时变量,包含函数形参和函数返回值。
程序映像中没有栈,在程序开始运行时也不会分配栈。每当一个函数被调用,程序在栈段中按函数栈框架入栈,就分配了局部变量存储空间。如果这些变量有初始化,就会有赋值指令给这些变量送初值,否则变量的值就呈现随机性。当函数调用结束时,函数栈框架出栈,函数局部变量释放储存空间。
栈的储存特点决定了C程序中所有局部的非静态变量的储存方式是动态的。函数调用时得到分配,赋予初值;函数调用结束之后释放空间,变量不复存在。下次调用时在重复这一过程。
每一个栈分配的存储空间是比较小的,VS编译器一般默认大小为4kb,因此,局部变量分配的空间不能过大,否则会造成栈溢出,程序崩溃。比如定义一个长度很长的局部数组,这是不可取的。当然,程序员也可自行设定栈的大小。在VS编译器中,在“项目”->“属性”->“连接器”->“系统”页面中设定堆栈的大小。如下图:

程序在内存中的布局

5、堆(heap)

堆(heap)用来存放C程序中动态分配的存储空间。
程序映像中的没有堆,在程序开始运行时也不会分配堆,函数调用也不会分配堆。堆的存储空间分配和释放是通过指定的程序方式来进行的,即由程序员使用指令分配和释放,若程序员不释放,程序结束后可能由操作系统回收。
C语言程序可以通过使用指针、动态内存分配和释放函数来实现堆的分配和释放。程序可通过动态内存分配和释放来使用堆区,堆区有比栈更大的储存空间、更*的使用方式。
堆和栈的共同点是动态储存,处于两个区域的储存单元可以随时分配和释放,所以这些储存单元的使用特点呈现临时使用的特点。data段的特点是静态储存的,处在这个区域的储存单元随程序运行而存在,随程序结束被释放,相对于程序生命周期,data段存储单元的使用特点呈现持久性的特点。data段由于持久占有储存空间,因此大小会被操作系统限定,而堆可以达到空闲空间的最大值。
堆和栈的不同是分配方式的不同,栈是编译器根据程序代码自动确定的大小,到函数调用时有指令自动完成分配和释放的;堆则完全由程序员指定分配大小、何时分配、何时释放。堆的优点是分配和释放是*的,缺点是需要程序员自行掌握分配和释放的时机,特别是释放的时机,假如已经释放了还要引用堆会产生引用错误,或者始终没有释放会产生内存泄漏。

6、注意事项

(1)不要通过指针引用其他函数的局部变量,否则得到的值将是一个随机的值,这不是你想要的。
(2)不要在函数中定义尺度过大的变量,比如长度很长的数组,否则会造成栈溢出,程序崩溃。
(2)动态分配的内存空间被释放后,不能再引用,否则会出现引用错误。
(3)free函数只能释放动态创建的内存空间,如果释放其他非动态创建的内存空间,则会出现错误。

【注】:本文主要参考由姜雪锋、曹光前编著,清华大学出版社出版的《C程序设计》。这本书写的很好,我所遇到的很多问题,都可以从中找到答案。