作者简介:大家好,我是菀枯
支持我:点赞+收藏⭐️+留言
格言:不要在低谷沉沦自己,不要在高峰上放弃努力!☀️
大家有没有好奇过,我们写的代码是怎么变成电脑上的程序的呢❓
难不成电脑可以直接识别我们的代码,然后开始运吗❓
它背后到底进行了什么操作呢❓
今天就一起来探索一下,代码如何变成我们的可执行程序吧؏؏☝ᖗ乛◡乛ᖘ☝؏؏
#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
来个通俗易懂的解释吧,比如下面一段代码
#define MAX 1000
int main()
{
int arr[MAX];
return 0;
}
当程序开始运行的时候,编译器就进行预处理,将我在上面一段代码中的MAX换成1000。 这样如果我们以后想改数组的最大值,只需要将MAX后面的1000 替换掉就可以了
不知道大家有没有好奇过,我们写代码的时候,为什么需要在开头写上#include
**其实,像stdio.h, stdlib.h这样的标准库中存有许多函数。**比如我们使用的scanf()和printf(),我们虽然没有自己去定义这些函数,但是依旧可以正常的使用,这就是因为编译器会将我们所包含的这些标准库展开,然后去寻找这些函数的定义。
我们来看一个例子:
可当我们把头文件展开后,看看变成了多少
从短短的八行变成了10691行,所以可以理解为什么一些程序加了这些标准库后,编译的速度会变慢
首先我们先介绍一下条件编译,我相信大部分小白都和我一样,之前应该都没有听过这个名词
C语言源程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译
听起来很迷茫对不对,我举几个例子就明白了,其实很简单
#include
#define __TEST__
int main()
{
int a = 1;
#ifdef __TEST--
printf("%d", a);
#endif
return 0;
}
我来解释一下上面的例子,#ifdef我们可以把它想成if, 如果____TEST____被#define了, 我们就编译它和#endif之间的部分,否则我们就不编译了
让我们来康康两个例子
当我们定义____TEST____时可以清楚的看到,当程序预处理完成后,printf()被加入到代码中。
可以清楚的看到,我们所写的printf()消失不见了。这就是条件编译的作用,在程序预处理的时候就对原代码进行一些修改,可以帮助我们对代码进行一些优化。
条件编译远不止这么简单,它还有一些其他的命令,但是我们这次学习的主要内容是一个代码是如何变成一个可执行文件的,所以我们就不在此停留了,感兴趣的小伙伴可以自行查阅资料。
狭义的编译,特指把.c源代码变成汇编指令。具体涉及词法分析、语法分析、语义分析、中间代码生成、目标代码优化等编译原理相关知识。
汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。
还是以我们刚刚的程序举例子吧,我们知道电脑是只能识别机器语言的(也就是是一堆二进制数)。在把我们的程序代码转换成二进制数前,我们会将程序转换成汇编代码作为过渡。
中间的mov,sub,push等都是汇编操作,而前面的是操作地址。通过这种方式,我们就在机器指令的操作码的表上来找到对应汇编代码的二进制数,这样我们就可以更容易的将其转换成二进制数。
汇编就是把所有的汇编代码变成二进制码的过程。汇编的时候会生成一个符号表,这个符号表记录着函数的符号。
因为我们在写一个程序的时候,经常会分文件写。会出现函数定义在其他的文件,然后被主函数调用的情况。编译器对这种问题的处理方式就是,在汇编主函数时,我把这个不认识的字符串先存起来,再去其他文件里寻找有没有名字一样的。找到了我们就使用,找不到我们就报错。
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存去执行。
一个项目里的每一个文件就好像火车的一个车厢,而主函数就是那个车头,链接器会通过之前的符号表来将他们以正确的方式合并起来。这样我们的火车就可以正常的运行了,一个可执行程序也就此诞生。