一个.c文件,我们想要将其运行,其实是需要经过一系列的编译以及链接,那么什么是编译和链接呢?本文将带你了解。
1.翻译环境与运行环境
在ANSI C的实现中,是要经过两个环境:翻译环境与运行环境。
当我们在编译平台上进行编写代码并运行时,就需要将所写的代码传递给计算机,其实计算机并不懂得我们所写的代码,也就是我们所写的语言其实它是无法识别的。这时候就要通过翻译环境将源代码翻译成机器所认识和可执行的机器指令,也就是二进制指令。
那么在翻译环境里是怎么将源代码翻译成二进制指令的呢?那么我们就需要了解翻译环境的工作了。
2.翻译环境的工作
在翻译环境中,进行的工作其实有两个那就是编译和链接两个过程
但是如果再进行细分的话,编译过程也能分为三个过程:预处理,编译,汇编
总的来说,在翻译环境中,源代码需要进行:
预处理,编译,汇编,链接
以下以gcc为例,讲解源代码的翻译过程:
在预处理阶段,源文件和头文件都会被处理成为.i后缀的文件
预处理中,最主要的任务是处理源文件中#开头的预编译指令:#include,#define等。
所有的#define都会被删除,并展开所有的宏定义。
所有的注释都被删除。
添加行号和文件名标识,方便后续编译器生成调试信息。
或保留#pragma的指令。
预处理完成后,编译就会对预处理后的文件进行:词法分析,语法分析,语义分析及优化。
在语法分析中,源代码程序被输入扫描器,然后扫描器将代码中的字符分割成一系列的记号
紧接着就到了语法分析,语法分析器会将产生的记号进行语法分析,然后产生语法树,在语法树中节点为表达式。
最后是语义分析,由语义分析器进行完成,它会对表达式的语法层面进行分析,在这个阶段会报告错误的语法信息。
编译完成后,就到了汇编的过程,这时汇编器就会将代码转换成机器读得懂的机器指令,每一条汇编语句几乎都对应一条机器指令。机器会根据汇编指令和机器指令的对照表进行一一翻译,也不做指令优化。
总结:
.c文件经过预处理生成.i文件,
.i文件经过编译生成.s文件,
.s文件经过汇编生成.o文件,
最后是.o文件进行链接
至此,编译过程完成。
在链接过程,一堆文件会被链接在一起才能生成可执行的程序。
链接的主要工作:地址和空间的分配,符号决议和重定位等。
链接解决的是一个项目中多文件,多模块之间相互调用的问题。
下面是一个项目中的两个文件:
//这时text.c文件
extern int add(int x,int y);
extren int g_val;
#include<stdio.h>
int main(){
int a=10;
int b=10;
int sum=add(a,b);
printf("%d",sum);
}
//这是add.c文件
int g_val=2024;
add(int x,int y){
return x+y;
}
我们在编译过程以及了解到,每个源文件都是单独经过编译器生成对应的.o文件,
在text.c文件中,第一次使用add和g_val文件时需要知道他们的地址,但是text.c并不知道,
所以text.c在编译时会将他们的地址先进行搁置,等链接的时候由链接器在其他模块寻找add函数的地址,然后text.c中引用到add的地址的指令都会进行修正。这个过程叫做重定位。
3.运行环境的工作
程序必循载入内存中,这个过程一般是由操作系统进行完成的,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码代码置入只读内存来完成,嵌入式开发就是个很好的例子。
程序的执行开始,紧接着便调用main函数。
当程序终止时,main函数也分为正常终止,也有可能被意外终止。