生成hello可执行文件的步骤

生成hello可执行文件的步骤,第1张

从hello world开始

下面我们通过一个简单的程序进入

#include 

#define PI 3.14
int main()
{
    printf("hello world\n");    // hello world
    printf("%f", PI);

    return 0;
}

在Linux使用GCC来编译此程序

gcc hello.c -o
./a.out

以上其实经历了四个步骤

gcc -E hello.c -o hello.i	//预处理
gcc -S hello.i -o hello.s	//编译
gcc -c hello.s -o hello.o	//汇编
gcc hello.o -o hello        //链接

图片来自[程序喵大人]gcc a.c 究竟经历了什么?)

预处理

其实预处理主要 *** 作有这几个:

  • 将所有的#define删除,并展开所有的宏定义
  • 删除程序中所有的注释// /**/
  • 处理所有的条件编译,#if、#ifdef、#elif等
  • 处理所有的#include指令,把这些头文件的内容都复制到引用的源文件中
  • 添加行号和文件名标识,方便编译器产生警告及调试信息
  • 保留所有的#pragma编译器指令,因为编译器会使用他们

我们可以查看经过预处理生成的.i文件

发现PI已经被替换成了3.14,而上面是展开的头文件内容(许多行)

经过预编译后的.i文件不包含任何宏定义,因为所有的宏已展开,并且包含的文件也已经被插入到.i文件中,我们可以通过查看预编译后的文件来确定宏展开的问题。

编译
gcc -S hello.i -o hello.s	//编译

编译过程就是把预处理完的文件进行一系列 *** 作产生相应的汇编代码文件

可以看到hello.s文件里面都是汇编语言

下面讲解编译器的 *** 作,先举一个例子(图片来自[程序喵大人]gcc a.c 究竟经历了什么?)

array[index] = (index + 4) * (2 + 6);
  • 词法分析:扫描器简单的将源代码的字符序列分割成一系列的记号,记号一般分为关键字(typename)、标识符(变量)、字面量(数字、字符串)和特殊符号(+、=)
  • 语法分析:对记号进行语法分析,产生语法树(以表达式为节点的树),如果出现表达式不合法,比如括号不匹配、表达式缺少 *** 作等,编译器会报语法分析阶段的错
  • 语义分析:语法分析仅完成了对表达式的分析,但不知道表达式是否具有意义,比如C语言中给两个指针做乘法运算无意义。编译器所能分析的语义为静态语义,通常包括声明和类型的匹配,类型的转换。比如将一个浮点数赋值给一个指针,语义分析程序会发现类型不匹配,编译器报错。动态语义一般指运行期出现的语义相关问题,比如将0作为除数是一个运行期错误
  • 中间语言生成:上述例子会将2 + 6直接替换得到8,然后运算,这是对语法树的优化。
  • 目标代码生成与优化:最后将目标代码生成汇编代码
汇编
gcc -c hello.s -o hello.o	//汇编

汇编器将汇编代码转变成机器可以执行的指令,每一个汇编语句都对应一条机器指令。

汇编器只是根据汇编指令和机器指令的对照表一一翻译即可

可以看到vscode显示hello.o文件是二进制文件,里面都是010101这些只有机器才看得懂的指令

链接
gcc hello.o -o hello        //链接

人们把每个源代码独立的编译,然后按照需要将它们组装起来,这个组装模块的过程就是链接

链接的工作就是把一些指令对其他符号地址的引用加以修正,链接过程主要包括了地址和空间分配,符号决议和重定位等步骤

静态链接

每个模块的源代码文件经过编译器编译成目标文件(.o/.obj),目标文件和库一起链接形成最终可执行文件。而最常见的就是运行时库,它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。

例子

如果我们在模块main.c中要使用func.c里的foo函数,那么我们在main.c的每一处调用foo的时候都需要知道它的地址。但在编译器编译main.c的时候它不知道foo的地址(单独编译),它暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。链接器根据引用的符号foo,自动取对应的fun.c模块查找foo的地址,然后将main.c模块中所有引用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。这就是静态链接的最基本的过程和作用。

如果我们不用链接器,我们就要手动的补充foo函数的地址,但是文件都是单独编译的,有可能fun.c文件编译后目标地址发生改变,那么所有引用该foo函数的地方我们都要一一修改,这是所不能忍受的,还是交给链接器去做比较舒服。

参考
  • 《程序员的自我修养——链接、装载与库》

  • [程序喵大人]gcc a.c 究竟经历了什么?

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/733620.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存