天天看点

【C语言进阶剖析】40、程序的内存布局

1 什么是程序?

2 程序与进程

3 程序的内存布局

4 程序术语的对应关系

5 小结

写完的 .c 文件是源文件。也叫源代码。

将源代码编译后,会生成可执行文件程序(linux下是.out,windows下是.exe)。这个文件就是程序。也叫作可执行代码。

源代码与可执行文件的对应如下,也就是程序文件的布局:

【C语言进阶剖析】40、程序的内存布局

可以看到,程序被编译器编译过后:

初始化的全变量和静态局部变量在 .data 段,未初始化的全局变量和静态局部变量在 .bss 段,函数等代码在 .text 段。

局部非静态变量存储在栈中,在右侧的图中没有对应,因为堆和栈是在程序运行开始后才正式存在,可执行程序中是没有栈、堆这样的存储区的。

file header 是一个文件头,操作系统根据这个文件头就能判断这是一个什么样的可执行程序,那也就知道了怎么样来运行这个程序。

在上一篇博客中讲到全局变量和静态变量位于静态存储区,那也就是说 .data 段和 .bss 对应着静态存储区。

前面一直说可执行程序,那么可执行程序在运行前和运行后有什么差异呢?,下面来看看程序与进程。

程序与进程不同

程序是静态的概念,表现形式为一个可执行文件

进程是动态的概念。程序由操作系统加载运行后得到的进程

每个程序可以对应多个进程,每个进程只能对应一个程序

问题:包含脚本代码的文本文件是一种可行性程序吗?如果是,对应什么样的进程呢?

脚本文件是一个文本文件,我们经常会说运行某个脚本文件。其实脚本代码是一种可执行程序,但不是一种直接的可执行程序,既然是可执行程序一定对应进程,对应的是什么进程呢,下面来说:

【C语言进阶剖析】40、程序的内存布局

一个可执行程序被操作系统加载运行后就得到了一个进程。对于 window 系统,双击一个 .exe 文件,操作系统加载这个文件,就得到一个进程。对于 linux 通过命令行加载,和 window 原理一样。这个进程的功能是确定的。

对于脚本文件,操作系统首先查看脚本文件对应的脚本解释程序,然后加载脚本解释程序,就得到了进程,这个进程会反过来读取脚本文件,这个进程的功能是不确定的,取决于它读取并解释的执行的脚本文件。脚本文件不能直接被操作系统加载,需要加载脚本解释程序得到进程,这样一个脚本文件最终还是对应了一个进程。

可执行程序和加载后进程的布局如下:

【C语言进阶剖析】40、程序的内存布局
file header 是告诉操作系统如何运行这个程序,所以加载后进程中就没有 file header 了。

各个段的作用:

堆栈段在程序运行后才正是存在,是程序运行的基础

.bss 段存在的是未初始化的全局变量和静态变量,以及所有被初始化为 0 的全局或静态变量,在目标文件中,这个节不占据实际的空间,他仅仅是一个占位符,这是为了空间效率。

.data 段保存的是已经初始化的全局变量个静态变量

.text 段存放的是程序中的可执行代码

.rodata 段存放程序中的常量值,如字符串常量

静态存储区通常指程序中的 .bss 和 .data 段

只读存储区通常指程序中的 .rodata 段

局部变量所占空间为栈上空间

动态空间为堆中的空间

程序可执行代码存放于 .text 段

问题:同时全局变量和静态变量,为什么初始化和未初始化的保存在不同段中?

c 规定,未初始化变量的初值为 0,这个清 0 的操作是由启动代码完成的,还有已初始化变量的初值的设置,也是由启动代码完成的。

为了启动代码的简单化,编译链接器会把已初始化的变量放在同一个段:.data,这个段的映像(包含了各个变量的初值)保存在“只读数据段”,这样启动代码就可以简单地复制这个映像到 .data 段,所有的已初始化变量就都初始化了。

而未初始化变量也放在同一个段:.bss,启动代码简单地调用 memset 就可以把所有未初始化变量都清0。

1、程序源码在编译后对应可执行程序中的不同存储区

2、程序是静态概念,进程是动态概念

3、堆栈段是程序运行的基础,只存在于进程空间中

4、程序可执行代码存放于 .text 段,是只读的

5、.bss 和 .data 段用于保存全局变量个静态变量