程序的翻译环境和运行环境
在C语言中,一个源文件代码(.c为后缀的文件)变为可执行文件(windows为:.exe后缀)的过程中经历了什么步骤?
首先有两个环境:一个翻译环境,一个运行环境;
翻译环境:就是把源代码翻译为计算机可以识别得懂得01二进制代码;
运行环境:就是执行代码,运行代码的一个环境;
翻译环境完成的工作为:编译+链接的过程;
而编译又分为:预处理,编译,汇编三个步骤;
所以:一个源文件变为可执行文件中经历的一下的过程;
先将源文件翻译为计算机可以识别的机器指令,然后再让及算计执行这些指令;
翻译的过程主要使编译+链接;而编译又分为三个步骤:预处理,编译,汇编;
我们平时写的源代码,要形成可执行程序,需要经过编译器编译为目标文件,然后由链接器链接目标文件成为可执行文件;
编译器:主要使完成翻译环境阶段的编译功能;
链接器:主要使完成翻译环境阶段的链接功能;
程序的翻译环境
预处理
而预处理主要做的就是:
- 头文件的展开(#include);
- 宏替换(#define);
- 条件编译的处理(#if #endif等);
- 去注释;
将.c文件变为预处理后的.i临时文件
在Linux 中我们可以通过
gcc
编译器来查看编译的过程;
而gcc -E
是使得源文件编译到预处理阶段停下来;
先创建一个程序:main.c源文件
注意该程序有:
头文件包含#include<stdio.h>
宏定义#define X 10 #define Y 20
注释: //this is a add ;
#include<stdio.h>
#define X 10
#define Y 20
int main()
{
//this is a add ;
int sum = X + Y;
printf("sum = %d\n",sum);
return 0;
}
预处理操作后,会将main.c,变成临时文件 main.i;
通过:gcc -E main.c -o main.i
命令:使得源文件从main.c变成main.i的临时文件,这个是预处理的结果:
对比两个文件:发现在预处理后的 main.i文件中。头文件展开了;宏替换了,注释也去掉了
编译
而在编译阶段主要做的事:
- 词法分析;
- 语义分析;
- 语法分析;
- 符号汇总;
将对每个.i文件单独转化为.s文件,也是对每个.i文件进行单独编译
通过:
gcc -S main.i -o main.s
命令后,使得.i到编译阶段处理完后就停下来,并且生成.s的汇编文件;
也可以通过gcc -S main.c -o mian.s
只不过和上面的区别一个是从.c开始,会先预处理,再编译;而一个是从预处理后的.i开始,直接编译就可以了;
符号汇总:汇总的是全局的符号:(比如说函数名,全局变量);
汇编
而在汇编阶段的主要工作:
- 形成符号表;
将.s的汇编文件变为.o的目标文件,该.0目标文件都是二进制的的信息,而该.o目标文件存储的格式在linux中为elf格式;
而elf格式是把文件分为好几个段(目前有点抽象,先知道有这个概念先)来存储文件信息的;比如.o目标文件就是被分为好几个段存储的elf文件,在Linux 可以通过 readelf
的命令查看elf格式的文件信息
通过
gcc -c main.s -o main.o
的方式将汇编文件.s编译为.o的目标文件;
目标文件都是二进制文件,用vim打开是用文本的格式打开,所以看到的就是一堆乱码
形成的符号表的意思是:给全局的符号一个地址,以便在链接的阶段找到;在每一个.o文件都会形成自己的符号表,这个符号表的符号是来自于编译阶段的.s文件完成的符号汇总工作得来的
可以通过
readelf main.o -a
观察 符号汇总的信息
上图就是我们的.o文件符号汇总的 main printf 的两个全局符号
链接
而在链接阶段主要做的事情是:
- 合并段表;
- 合并符号表和符号表的重定位;
把每个目标文件.o合并成为一个可执行程序(Linux默认是a.out文件);
合并段表的意思是:将每个单独的.o文件的段表信息合并成为一个段表信息:
比如一个工程由两个源文件:add.c 和 main.c文件他们的目标文件为 add.o 和main.o;
每个目标为文件都是elf格式的文件,而elf格式的文件都是以段的信息存储信息的;
在链接阶段,就是把add.o段表的信息和main.o段表的信息合并成为一个段表信息;
合并符号表的意思是:将每个单独的.o文件形成的符号表合并成为一个符号表;
比如也是个工程由两个源文件:add.c 和 main.c文件他们的目标文件为 add.o 和main.o;
每个目标文件都在汇编阶段生成了自己的符号表:比如add.o文件中有个符号表 add 0x123(只是举例,add为加法函数实现符号);在main.o的符号表为main 0x100 ;add 0x111(这是函数声明的符号):将两个文件的符号表合成一个符号表 add 0x123 main 0x100 (保留了实现函数的符号,去掉了声明的符号)
符号表的重定位的意思是:在合并符号表的过程,会将无效的符号去掉,保留有效的符号。
比如函数声明的符号会去掉,留下有效的函数实现的符号;
运行环境
可执行文件的运行是在运行环境中运行的;
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
总结
这里只是大致的讲了一下程序从编译链接到运行的阶段干的事情,也是初略的讲讲的;
如果需要细致了解更多的细节:可以关注一本书《程序员的自我修养》里面由对程序的编译链接做了非常详细的阐述,难得的一本好书。