谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)

文章目录

  • 程序的翻译环境和运行环境
  • 程序的翻译环境
    • 预处理
    • 编译
    • 汇编
    • 链接
  • 运行环境
  • 总结

程序的翻译环境和运行环境

在C语言中,一个源文件代码(.c为后缀的文件)变为可执行文件(windows为:.exe后缀)的过程中经历了什么步骤?

首先有两个环境:一个翻译环境,一个运行环境
翻译环境:就是把源代码翻译为计算机可以识别得懂得01二进制代码

运行环境:就是执行代码,运行代码的一个环境;

翻译环境完成的工作为:编译+链接的过程

编译又分为:预处理,编译,汇编三个步骤;

谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第1张图片
所以:一个源文件变为可执行文件中经历的一下的过程;

先将源文件翻译为计算机可以识别的机器指令,然后再让及算计执行这些指令;
翻译的过程主要使编译+链接;而编译又分为三个步骤:预处理,编译,汇编;


我们平时写的源代码,要形成可执行程序,需要经过编译器编译为目标文件,然后由链接器链接目标文件成为可执行文件
编译器:主要使完成翻译环境阶段的编译功能;
链接器:主要使完成翻译环境阶段的链接功能;
谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第2张图片


程序的翻译环境

预处理

预处理主要做的就是:

- 头文件的展开(#include);
- 宏替换(#define);
- 条件编译的处理(#if #endif等);
- 去注释;
将.c文件变为预处理后的.i临时文件


在Linux 中我们可以通过 gcc编译器来查看编译的过程;
gcc -E是使得源文件编译到预处理阶段停下来;

先创建一个程序:main.c源文件
注意该程序有:
头文件包含#include
宏定义#define X 10 #define Y 20
注释: //this is a add ;

#include 
#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的临时文件,这个是预处理的结果:
谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第3张图片
对比两个文件:发现在预处理后的 main.i文件中。头文件展开了;宏替换了,注释也去掉了


编译

而在编译阶段主要做的事

- 词法分析;
- 语义分析;
- 语法分析;
- 符号汇总;
将对每个.i文件单独转化为.s文件,也是对每个.i文件进行单独编译


通过:gcc -S main.i -o main.s 命令后,使得.i到编译阶段处理完后就停下来,并且生成.s的汇编文件;
也可以通过gcc -S main.c -o mian.s 只不过和上面的区别一个是从.c开始,会先预处理,再编译;而一个是从预处理后的.i开始,直接编译就可以了;

谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第4张图片


符号汇总:汇总的是全局的符号:(比如说函数名,全局变量)


汇编

而在汇编阶段的主要工作

- 形成符号表;
将.s的汇编文件变为.o的目标文件,该.0目标文件都是二进制的的信息,而该.o目标文件存储的格式在linux中为elf格式;

而elf格式是把文件分为好几个段(目前有点抽象,先知道有这个概念先)来存储文件信息的;比如.o目标文件就是被分为好几个段存储的elf文件,在Linux 可以通过 readelf 的命令查看elf格式的文件信息


通过 gcc -c main.s -o main.o的方式将汇编文件.s编译为.o的目标文件;
目标文件都是二进制文件,用vim打开是用文本的格式打开,所以看到的就是一堆乱码

谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第5张图片


形成的符号表的意思是:给全局的符号一个地址,以便在链接的阶段找到;在每一个.o文件都会形成自己的符号表,这个符号表的符号是来自于编译阶段的.s文件完成的符号汇总工作得来的

可以通过 readelf main.o -a 观察 符号汇总的信息

谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第6张图片
上图就是我们的.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 (保留了实现函数的符号,去掉了声明的符号)

符号表的重定位的意思是:在合并符号表的过程,会将无效的符号去掉,保留有效的符号。

比如函数声明的符号会去掉,留下有效的函数实现的符号;


运行环境

可执行文件的运行是在运行环境中运行的;

程序执行的过程

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

总结

这里只是大致的讲了一下程序从编译链接到运行的阶段干的事情,也是初略的讲讲的;
如果需要细致了解更多的细节:可以关注一本书《程序员的自我修养》里面由对程序的编译链接做了非常详细的阐述,难得的一本好书。

谈谈C语言从源文件变为可执行文件之间发生的过程详解(C程序的编译链接运行)_第7张图片


你可能感兴趣的:(C语言,c语言,c++,linux)