【C语言】编译链接

一.情景引入

大家写下的第一个代码应该都是这样的吧:

#include<stdio.h>

int main()
{
	printf("hello world!\n");
	return 0;
}

我们点击编译器的编译运行,会发现屏幕上神奇地出现了一串“hello world!”字符,我们可能会好奇编译运行时电脑都做了什么,让一个文件转换成这样一句话的呢?

这就要不得不提到C语言编译的流程了。如图为一个源代码编译生成可执行文件的过程。
在这里插入图片描述
想象一下,你的代码就像一个准备远行的旅客,从你的编辑器出发,要经历层层关卡,最终变成能在 CPU 上驰骋的机器指令。这个过程主要分为四个阶段:预处理、编译、汇编、链接。
预处理:就像是为你的代码”收拾行李“。
编译:将代码翻译成汇编语言。
汇编:再将汇编语言转换成机器码。
链接:将各个部分组装在一起。

二.预处理

预处理的工作是由预处理器完成的,它的任务包括:

1.展开所有#include指令(复制头文件内容)

如图,左侧是一个最基础的c语言文件,右侧是将该文件预处理后得到的.i文件(使用Linux下gcc编译器生成的)
在这里插入图片描述
我们会发现,左侧代码仅有7行,而它预处理完之后竟然有八百多行,这是因为在预处理阶段,将#include<stdio.h>包含的头文件复制到了代码中,使得代码量增加许多。

2.替换所有宏定义(#define)

在这里插入图片描述
图片左侧是源代码,右侧是预处理得到的.i文件,我们会发现不仅将头文件展开了,还将宏定义“#define M 100”语句删除,将代码里的M替换成了100。

3.处理条件编译指令(#if、#ifdef等)

#if的用法:

#if 整型常量表达式1
    程序段1
#elif 整型常量表达式2
    程序段2
#elif 整型常量表达式3
    程序段3
#else
    程序段4
#endif

如果表达式1的值为真,就对程序段1进行编译,否则就计算表达式2,结果为真的话就对程序段2进行编译,为假的话就继续往下匹配,知道遇到值为真的表达式,或者遇到 #else,这一点和 if else 非常类似。需要注意的是 #if 命令要求判断条件为整型常量表达式,即表达式中不能包含变量,而且结果必须是整数。

#ifdef的用法:

#ifdef  宏名
    程序段1
#else
    程序段2
#endif

如果宏名被定义过,就对程序段1进行编译,否则就对程序段2进行编译。

4.删除注释内容

在这里插入图片描述
我们再在.c文件的代码中随便加入几行注释,预处理后会发现所有的注释内容都被删除了。

所以预处理做的其实就是"文本替换"工作!,它不关心语法对不对,只是忠实地执行替换、展开、条件判断这些"文本操作"。就像一个不懂厨艺的助手,只会按照你说的准备食材,不管这些食材最后能不能做成一道菜!

三.编译

预处理完成后,编译器开始工作了。编译就是将预处理之后的文件进行一系列的词法分析、语法分析、语义分析及优化,将 C 代码转换成汇编代码,并生成对应的编译代码文件。
汇编语言更接近机器语言,但还是人类可读的。

如图左侧是源代码,我们将它编译后生成的.s文件打开,就是右侧部分代码。
在这里插入图片描述
看不懂?没关系!这就是汇编语言,它直接对应 CPU 的操作。简单解释一下:

call printf : 相当于调用printf函数
movl $100, -4(%rbp) :相当于a=100

这一步是真正的"翻译"过程,编译器要理解你 C 代码的意思,然后用汇编语言重新表达出来。这就像是将英文翻译成法文——意思一样,但表达方式完全不同了。

四.汇编

汇编就是将汇编代码转换成机器码,也就是由0和1组成的二进制代码。汇编器根据汇编指令和机器指令的对照表一一进行翻译,也不做指令优化。
我们将上述.s文件汇编生成.o文件,在Linux上用“ hexdump -C main.o | head ”指令打开。(无法直接查看汇编文件)
如图所示:
在这里插入图片描述
这就是机器语言,是 CPU 直接执行的指令。
想象一下,如果汇编语言是乐谱,那么这一步就是把乐谱变成了音乐播放器能直接播放的 MP3 文件。人类很难直接"读懂"它,但计算机却能立刻明白这些指令的含义。

五.链接

链接是一个相对复杂的过程,由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接时链接器的工作是:
1. 把所有目标文件合并成一个 。
2. 解析所有符号引用(比如main.o中对printf的调用) 。
3. 确定每个函数和变量的最终内存地址 。
4. 添加启动代码(在main函数执行前初始化环境)。

链接处理又分为静态链接和动态链接,
静态链接:
在编译时将库的代码直接复制到最终的可执行文件中。生成的文件独立性强,不依赖外部库,但体积较大。
动态链接:
在运行时从系统中加载共享库,可执行文件仅保存库的引用,体积小,多个程序可共享同一库。

这个阶段就像是拼图游戏的最后一步,把所有零散的片段拼接成一个完整的图像。你的代码、你朋友的代码、系统库的代码,全都在这一刻被组合在一起,形成一个可以独立运行的程序。

六.总结

让我们梳理一下完整的流程:
1.你写代码:创建main.c
2. 预处理:展开头文件和宏定义,生成main.i
3. 编译:将预处理后的文件转成汇编代码,生成main.s
4. 汇编:将汇编代码转成机器码,生成main.o
5. 链接:将目标文件和必要的库文件链接成可执行文件my_program。
大家刚接触C语言时可能只能看懂大概,没关系,学习本来就是个一路打怪升级,不断提升的过程,可以先点赞收藏,等进一步对编程了解了之后再看一遍,或许就能理解更多了。
谢谢阅读^_^!在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值