gcc编译器,以及源文件的翻译

📅 2026/8/14 15:35:00
gcc编译器,以及源文件的翻译
gcc与翻译1、翻译的过程1.1、预处理1.1.1、两个小问题1.2、编译1.3、汇编1.4、链接1.5、一个小细节2、条件编译2.1、条件编译举例2.2、理解条件编译2.3、为什么要有条件编译3、翻译的整个过程的由来3.1、指令集简单理解3.2、开关3.3、二进制纸带编程3.4、汇编语言3.5、C语言等高级语言4、编译器与语言的自举过程5、链接的初步理解5.1、库的简单认识5.2、链接的简单认识5.3、感性认识动态库 vs 静态库 动态链接 vs 静态链接5.3.1、第一个故事5.3.2、第二个故事5.3.3、总结5.4、两个小细节编译器的作用是把源文件翻译成可执行二进制文件。其中gcc只能编译C程序g可以编译C / C程序1、翻译的过程翻译的过程大致分为预处理编译汇编链接1.1、预处理预处理阶段编译器对源代码进行头文件展开宏替换注释替换成空格条件编译(之后会提及)当前我们以准备好的code.c源代码为例code.c里有头文件、宏、注释预处理执行指令gcc -E code.c就完成了code.c的预处理工作但是这样只能将编译结果放在打印屏幕上。我们可以执行指令gcc -E code.c -o code.i将预处理后的内容放到新建的文件code.i中vim打开code.i文件就可以看到预处理之后的结果。这里我们用到的选项有-E编译器开始翻译源文件完成预处理就停下-o将预处理结果写入code.i文件1.1.1、两个小问题头文件展开我们之前认为预处理阶段编译器会将头文件展开可“展开”这一行为到底有什么含义我们可以vim打开文件/usr/include/stdio.h通过将/usr/include/stdio.h文件与code.i进行对比我们可以发现有许多相似之处。Linux下编译器进行头文件展开本质上是将/usr/include目录下同名头文件内容拷贝到源文件中从而进行头源合并。其实学到后面我们就能体会到只有预处理阶段才需要头文件。预处理后的文件还是C语言文件吗答案是还是的只不过更简洁了注释被去掉了宏被替换成了具体的内容头文件也被替换拷贝上了具体的内容。1.2、编译编译的过程是将预处理完的文件翻译成汇编语言的文件。指令为gcc -S code.i -o code.s其中-S编译器开始翻译源文件完成编译就停止。意味着我们不仅可以对已经完成预处理的code.i使用-S选项完成编译还可以对源文件code.c使用-S选项一键编译。code.s汇编语言文件的后缀一般是.s。1.3、汇编汇编的过程是将汇编语言文件处理成目标文件(.o / .obj)目标文件又称为可重定位二进制目标文件。指令为gcc -c code.c -o code.o其中-c编译器开始翻译文件完成汇编就停止。但是我们无法执行code.o文件。比如在code.o文件中我们想使用printf()函数可是code.o文件里并没有printf()函数的定义。这时我们就需要将code.o与C标准库链接形成一个可执行文件。1.4、链接当我们执行当前目标文件需要第三方库的时候我们就需要将当前文件与第三方库进行链接。指令为gcc code.o -o code.exe也可以一步到位gcc code.c -o code.exegcc -o code.exe code.c这时code.exe就可以执行了。1.5、一个小细节-c选项以后我们更不常使用预处理、编译指令更常使用汇编指令生成目标文件。这是因为当我们有很多文件需要同时链接成一个可执行程序时如果某一些文件需要修改我们就可以针对那些文件专门进行修改、处理成目标文件再链接在一起。这样对于单个文件的操作就避免了多次链接并且使得修改过程更清晰从而提高效率。2、条件编译预处理阶段编译器做的其中一件事是处理条件编译。2.1、条件编译举例这里给出一段包含条件编译的C语言程序main.c我们可以简单解释上面出现的条件编译语句预处理阶段编译器如果识别到宏V1Version1()语句会被保留Version2()语句会被裁剪如果没有识别到宏V1Version2()语句会被保留Version1()语句会被裁剪。而#endif则会提醒编译器结束条件编译。如何验证上面的解释生成main.i文件查看即可。main.c包含宏V1main.c不包含宏V12.2、理解条件编译通过上面的学习我们知道条件编译的功能是在预处理阶段提示编译器进行代码裁剪。其实井号#以及之后的内容比如#ifdef #else #endif都属于预处理符是为了提醒编译器要进行代码裁剪以及提醒编译器如何裁剪代码。2.3、为什么要有条件编译我们之前使用的vs 2022以及我们现在使用的Xshell其实使用的是免费版 / 社区版是不收费的。而像vs 2022、Xshell及其它开发环境(软件)除了不收费的版本还有付费版 / 企业版。我们很容易想到这些软件的免费版相当于付费版去除了某些功能只保留了基础的功能。那么问题来了这些软件大多数都是由C / C语言编写的那么这些设计并维护软件的厂商到底是选择维护一份免费版代码一份付费版代码还是选择只维护付费版代码然后想办法给付费的用户开放所有功能而给普通用户阉割掉一些高级功能当然是选择后者。因为选择前者意味着要维护两份代码(甚至多份代码)投入成本高而选择后者只需要维护好一份代码即可。这时条件编译裁剪代码的功能就派上用场了。付费用户(不包含宏FREE)输出的结果注意到当前代码其实是没有宏FREE的。我们可以不通过vim改写代码而是直接在Bash命令行上输入指令gcc main.c -o main -DFREE1就可以添加宏FREE进入main.c这是免费用户(包含宏)的输出结果。条件编译不仅可以实现软件商业化的目的还可以对源代码进行多功能开发使之使用各种不同的场景。比如Linux源代码就使用了条件编译所以Linux系统可以运作在服务器、车载系统、电视以及板子上。3、翻译的整个过程的由来也许有人会好奇源文件为什么要经过预处理、编译、汇编、链接这么多操作才能翻译成可执行文件(不可以直接由源文件直接一步到位翻译成可执行文件吗)这就需要我们稍微简单地回顾编译器的历史以及编程语言的历史了。3.1、指令集简单理解首先我们可以来想象一个场景刚出生的小孩什么也不会不会站、不会坐、不会走路、不会抓握也听不懂人说的话。经过父母一年的训练这个小孩学会了基本的动作坐、站、走路、抓握以及语言的输入与输出。有一天小孩的叔叔来到他家对小孩说小孩乖哦给叔叔拿一杯水。小孩听到了叔叔的话于是进行了一系列动作站起来、走到桌前、拿起水杯、走到叔叔声旁然后递水给叔叔。在这个场景中小孩对应计算机中的CPU父母给小孩进行训练对应技术人员给一块裸的CPU硅片刻上指令集叔叔给小孩发号施令对应向计算机输入指令小孩进行一系列动作对应计算机CPU进行一系列计算操作所以我们知道了计算机CPU上存在指令集指令集包含一系列操作指令。由于计算机CPU只能读懂二进制代码指令集包含的操作指令就是二进制代码。3.2、开关计算机最开始出现时使用的是开关这么一个使用的技术。我们可以理解为程序员利用通电与断电表示出一个个二进制序列进而表示一个个操作指令将这些操作指令(以及数据)输入计算机计算机就会进行一系列计算操作得出结果。但是开关技术就将一些具体的功能写死了换句话说想要实现一种功能就必须特别实现一种操作体系。这就导致就算多种操作体系之间有相同的操作我们也无法实现操作的复用只能有几个问题就单独设计几个操作体系。3.3、二进制纸带编程当时的科学家觉得开关太死板了于是他们想出了另一种办法二进制纸带编程。二进制纸带编程就是在纸带上打洞将二进制指令刻在纸带上然后通过传感器扫描纸带将指令输入计算机进行操作得出结果。这意味着操作指令的设计在纸带上完成就可以了计算机只管对输入的指令进行操作。二进制纸带编程一定程度上提高了效率。可是另一个不可忽视的问题日渐凸显纸带上指令的设计需要我们清楚操作对应的二进制指令。二进制指令都是01组成的序列抽象无比难以记忆。有没有一种方法能够将操作的二进制指令与操作本身的一些特性对应起来以方便理解比如用操作的单词代表操作指令3.4、汇编语言后来人们又发明了汇编语言。汇编语言就利用了指令的英文单词替代了指令的二进制序列。比如mov移动数据add相加push压栈call调用函数英文单词可比01序列方便记忆多了。汇编语言相当于对二进制指令集做了一层包装。但是汇编语言还是麻烦了我们可能需要几十条指令才能完成一个简单的操作比如变量的创建。还有没有方法使得程序的设计更贴合人类的语言习惯所以C语言应运而生。3.5、C语言等高级语言C语言的使用更符合人类的语言习惯。然而C语言也有缺点与不足。随着时代的发展C、java、python、PHP等一些高级语言也逐渐登上计算机编程历史舞台。所以C语言源文件的翻译并没有直接一步到位。我们可以将C语言翻译成汇编语言然后借助之前积累的有关汇编语言的技术最终完成源文件向可执行程序的转变。4、编译器与语言的自举过程汇编语言本质上是一套规则。我们只有借助规则才能设计出程序乃至项目从而完成我们预期的目标。所以是先有汇编语言。然而当汇编语言刚出现的时候由于还没有针对汇编语言的编译器我们还无法对编写的汇编语言进行翻译得到可执行文件。这时我们可以用低一级的语言即二进制代码设计出第一代汇编语言编译器。以后第一代汇编语言编译器逐渐完善支持新功能我们就可以按照新功能设计出第二代汇编语言第二代汇编语言就可以设计出第二代汇编语言编译器第二代汇编语言编译器又支持了新功能就可以设计出第三代汇编语言…也就是说编译器支持新功能汇编语言就可以增加新语法增加了新语法的汇编语言又可以反过来设计新的编译器。这就是编译器与语言的自举过程。同理C语言的第一代编译器是由汇编语言编写的接下来我们就可以通过C语言与C语言编译器的自举来进行C语言与C语言编译器的迭代更新。5、链接的初步理解源文件翻译的链接阶段中需要将众多.o文件与链接库链接。那么这个库到底是什么5.1、库的简单认识我们之前就提到过我们在源文件代码中调用了printf()函数而我们没有在源文件定义printf()函数所以我们必须链接当前环境提供的C语言标准库在标准库中有printf()的定义。这个printf()的定义也许是曾经某个参与C语言开发的顶级大佬设计的。现如今全世界的编写程序的人都在使用这个大佬写的printf()而不是自己定义一个printf()。所以库本质上是程序员之间的一种协作方式。库能提高编程效率。那么库在哪里其中libc.so就属于C标准库。其实libc.so是C语言的动态库。windows下也有库。以下是Linux与windows的库后缀的区分系统动态库静态库Linux.so.awindows.ddl.lib我们也可以分析出Linux下库命名的规则lib [真正名称] .[a / so]5.2、链接的简单认识当我们创建了一个C语言文件soft.c编译后对可执行文件执行ldd soft我们会观察到ldd指令的作用是查看当前程序依赖的库。其实有不少指令都依赖C / C标准库。我们之前提到过库分为动态库和静态库其中动态库通常与程序进行动态链接静态库通常与程序进行静态链接目前的知识依旧无法支持我们较为全面地了解动态库 / 动态链接与静态库 / 静态链接我们就来感性认识它们5.3、感性认识动态库 vs 静态库 动态链接 vs 静态链接5.3.1、第一个故事你是小于你以优异的成绩考入了临川二中。中考之后的暑假你既惊喜又担忧担忧什么呢临川二中以半军事化管理方式、考试很多闻名当地学校是肯定不能带手机的而你又想很喜欢上网很喜欢玩游戏。于是你向老爸求助老爸装模做样地说“想玩游戏那怎么行~我给你介绍隔壁小帅小帅今年高考考入了清北大学。你向他取取经”于是小于你与小帅学长见了面。你对小帅学长说你不担心自己的成绩你只担心自己上学期间上不了网玩不了游戏。小帅学长说那好办就直接给了你条详细的地址那里是学校附近一所网吧。开学后小于你平时照常上课周日时你给自己制定了一个计划900 ~ 1000 写作文1000 ~ 1100 学习化学1100 ~ 1200 学习数学1200 ~ 1230 吃饭1230 ~ 1430玩游戏1430 ~ 1630 睡觉······问题是学校不让带手机学校里就玩不了游戏。你想起了小帅学长给的地址。你按照地址来到了网吧。你跟老板说你要上网老板就领你到号码为1234的电脑前让你开始上网。于是你度过了一个愉快的下午。渐渐地班上同学以及其他班上同学都知道了这家网吧纷纷都偷偷来这家网吧上网玩游戏.。后来的某一次月考不少班级的成绩一落千丈包括小于你所在的班级。临川二中的老师们都很生气一问才发现成绩变差的学生都去过那家网吧频繁上网。于是老师们联名举报帽子大院的帽子叔叔们就将网吧查封了。最终同学们包括小于都上不了忘了。在上面的故事中小于和每一个同学就是目标文件临川二中就是内存小于周天的一个个行程就是目标文件对应源文件的要执行的代码网吧就是动态库号码1234的电脑就是执行代码需要的库函数小帅学长就是编译器中的链接器小帅学长向小于提供网吧的地址就是链接器对目标文件进行动态链接其实以这个故事类比动态链接还是不太严谨。后面我们对于动、静态库会有更详细的讲解。在这个故事中我们也会发现动态链接的一个很突出的缺点如果网吧被查封了就上不了网同理如果动态库丢失编译成的程序会无法执行。5.3.2、第二个故事网吧被查封了小于你很苦恼别人上网吧成绩掉了我成绩又没掉网吧没了我还怎么上网于是小于你告诉老爸叫老爸想想办法。给力的老爸直接找到校长软磨硬泡让校长答应了在你的宿舍桌上安装电脑与此同时老爸联系了之前网吧被查封了的网吧老板将你之前用过的1234号电脑直接买了过来装在了你的宿舍。这样你就有了一台自己的电脑。其他同学听说了也想效仿小于你在宿舍装一台电脑。渐渐地所有同学的桌前都有了一台电脑。在这个故事中电脑就是静态库一台台被需要的电脑组成了静态库老爸和网吧老板就是(使用静态库的)链接器给小于和其他每一个同学装电脑就是在给目标文件进行静态链接5.3.3、总结讲到这我们也许就能对动态库与静态库、动态链接与静态链接有了一个大概的认识动态链接就是将库函数的地址写入到目标文件然后与动态库链接到一起做成可执行程序。其中相同的库函数在不同的程序中是可以共享的。静态链接就是将库函数直接拷贝到目标文件做成可执行程序。若不同的程序需要相同的库函数那么连接后每一个程序都有独属于自己的库函数尽管这些库函数都是完全一样的。那么动、静态链接也就可以简单做一个比较连接方式动态链接静态链接依赖库的程度动态库丢失或者出问题链接后的程序无法执行静态库中库函数拷贝到了程序中文件大小较小较大5.4、两个小细节如果删除C动态库会怎样结果是很可能大部分指令用不了。因为大部分指令都是C / C编写的。gcc默认使用的是动态链接我们可以理解为系统认为动态链接形成的可执行程序体积更小存储使用效率更高于是系统默认就用动态链接。强制进行静态链接需要加上选项-static。有人可能静态链接完毕后发现C / C程序无法执行。大概率是系统没有静态C / C库可以搜索指令安装静态库。