在xampp下搭建本地网站,seo优化怎么做,wordpress 主题 cdc,哪里有服务好的深圳网站建设【Linux系统】—— 编译器 gcc/g 的使用 1 用 gcc 直接编译2 翻译环境2.1 预处理#xff08;进行宏替换#xff09;2.2 编译#xff08;生成汇编#xff09;2.3 汇编#xff08;生成机器可识别代码#xff09;2.4 链接2.5 记忆小技巧2.6 编译方式2.7 几个问题2.7.1 如何理… 【Linux系统】—— 编译器 gcc/g 的使用 1 用 gcc 直接编译2 翻译环境2.1 预处理进行宏替换2.2 编译生成汇编2.3 汇编生成机器可识别代码2.4 链接2.5 记忆小技巧2.6 编译方式2.7 几个问题2.7.1 如何理解条件编译2.7.2 为什么编译器要先将代码翻译成汇编语言 1 用 gcc 直接编译 我们平时学的 C/C 代码都是文本文件但是我们知道计算机只认识二进制因此我们需要将C/C代码翻译成二进制文件 在 Windows 系统中编辑代码和翻译过程我们都是用 VS 进行的因为 VS 是集成的IDE环境那么在 Linux 中我们又该如何完成代码的翻译工作呢 在 Linux 中我们用到的编译器是 gcc/g其中gcc是对C语言进行编译g是对C进行编译。因为 gcc 和 g 的指令操作等完全一样本文主要是用 gcc 进行演示。 我们创建一个 test.c 文件写上代码
#includestdio.hint main()
{printf(hello world\n);printf(hello Linux\n);return 0;
}如何用 gcc 对代码进行编译呢 指令如下 「gcc」 「要编译的文件」 gcc 会默认生成一个叫 a.out 的可执行文件 那如果我想指定生成文件的名字呢 有两种方法 「gcc 」「要编译的文件」 「-o」 「目标文件」「gcc 」 「-o」 「目标文件」「要编译的文件」 但是仅仅学会指令是远远不够的我们学习 gcc/g 更重要的是学习翻译过程背后的过程我们知道我们写的 C语言 代码最终要形成可执行程序要经过预处理、编译、汇编、链接这几个过程下面我们通过 gcc进一步认识这四个过程。
2 翻译环境
2.1 预处理进行宏替换 预处理阶段主要处理那些源文件中 # 开始的编译指令。比如# i n c l u d e include include# d e f i n e define define处理的规则如下 将所有的 # d e f i n e define define 删除展开所有的宏定义处理所有的条件编译指令如#if、#ifdef、#elif、#else、#endif处理 # i n c l u d e include include 预编译指令将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的也就是说被包含的头文件也可能包含其他文件。删除所有注释添加行号和文件名标识方便后续编译器生成调试信息等 我们创建一个code1.c文件、写一段代码 这一小段代码头文件、宏定义、注释以及条件编译都有了正好可以看看预处理的效果。 我们如何看到预处理后的结果呢我们来学一个选项 「-E 」 「-E 」进行程序翻译在预处理做完时停下来 指令如下 「gcc 」「-E 」「要编译的文件」 「-o」 「目标文件」 注预处理后的文件后缀为.i 我们用 vim 打开 code.i 来看看 我们发现注释不见了我们定义的宏M和N预处理后也消失不见了M直接别替换成100这叫做宏替换并且printf(hello N\n) 和 printf(no N\n)只剩下了printf(hello N\n) 。这是因为条件编译我们定义了N(定义了就行可以不写值)所以预处理后保留了printf(hello N\n)为什么我们的文件变大了呢根本原因就是头文件展开。 在编译的时候只要预处理完了头文件就可以不需要了。头文件展开的意思就是把你要包含的头文件全部拷贝至你的目标文件里形成 .i 文件。这不过这个 .i 文件我们现在将其打印出来并且写到文件里了如果不写的话它就是内存级的在编译器内部。同时 stdio.h 头文件中也包含其他的头文件因此它会类似递归式的拷贝。 因此一个你可能只写了几百行的代码预处理后可能有上千行。 我们 C语言 用到的众多头文件在系统中默认都是安装了的一般是存在 /usr/include 路径下 如我们包含的头文件 stdio.h 一般是存在/usr/include/stdio.h 路径下 可以用 vim 打开来看看 里面的代码近 900 行但是它有条件编译同时 stdio.h 中本身也包含了其他的头文件。 这里我问大家一个问题预处理后的 code1.i 还是 C语言 吗 答案还是C语言 不过他是一个已经预处理过是一个干净的C语言了。
2.2 编译生成汇编 编辑将C语音翻译成汇编语言 编译过后生成的汇编文件后缀为 .s 需要用到命令行选项 「-S 」 「-S 」从现在开始进行程序翻译在编译步骤做完时停下来 指令如下「gcc 」「-S 」「要编译的文件」 「-o」 「目标文件」 我们可以从 .c 到 .s 也可以从 .i 到 .s因为之前已经做过 .c 到 .i 了就不再重复做预处理步骤 直接 .i 到 .s 我们用 vim 打开 code1.s 2.3 汇编生成机器可识别代码 汇编是指通过汇编器将汇编代码转变成机器可执行的指令每一个汇编语句几乎都对应一条机器指令。就是按照汇编指令和机器指令的对照表一一的进行翻译也不做指令优化。 先直接上指令 「gcc 」「-c 」「要编译的文件」 「-o」 「目标文件」 如 gcc -c code1.s -o code1.o 「-c 」从现在开始进行程序翻译在完成汇编后停下来 以 .o 为后缀的文件全称叫可重定位目标文件也就是我们所说的目标文件。目标文件在 Windows 系统下是以 .obj 结尾的 目标文件是二进制文件因此我们打开它是啥都看不懂的 虽说 code1.o 已经是二进制文件但是它还是无法被执行的。因为目标文件仅仅是将我们自己写的代码编成二进制了可我们的程序中还包含着许多库方法如printf、scanf、STL容器此时我们的程序还没有和库方法关联起来比如我们用了 printf 方法可我们根本没有 printf 方法的实现所以我们的目标文件是跑不动的。 所以我们的程序还要经过最后一步链接才能形成可执行文件 2.4 链接 链接过程没有命令行选项 指令如下 gcc code1.o -o code1 这里我们并没有指定去链接哪个库因为我们现在的代码里没有使用任何的第三方库我们用的都是C语言标准库的方法gcc会帮我们去系统里找我这个程序用了 C语言 的哪个标准库。但如果我们要依赖某个第三方库就需要指定去链接了这点我们以后再介绍。 生成可执行序后程序就可以运行了 2.5 记忆小技巧 好像预处理、编译、汇编这三步的命令行选项很难记有什么记忆方法吗 他们分别是 「-E 」「-S 」「-c 」 合起来就是键盘左上角的「esc」键我只需要记住前两个是大写的就行了。 而预处理、编译、汇编这三步生成的文件后缀又怎么记呢 他们分别是『.i』『.s』『.o』 连起来就是iso我们可以记ios再将后面两个反过来
2.6 编译方式 一般我们在编译文件时不会像上面一样 .i、.s 、.o全部生成一遍上述这样做只是为了然我们了解整个翻译的过程。 我们编译文件的习惯是将所有的文件生成 .o 文件再将所有相关的 .o 文件一起打个包生成可执行文件 为什么喜欢这么做呢 主要原因是
编译器在编译时不仅仅要形成可执行程序还可能要形成库所谓的库其实就是把 .o 文件了个包如果要形成库的话就不需要编译性成可执行程序我们目前使用的 VS 最终就形成一个可执行程序但往往实践中可能形成 10 个、100 个可执行程序可能你有 1000 个源文件其中 100 个形成程序A、50 个形成程序B、60 个形成程序C……我们需要将所有的 .o 做自由组合形成多个可执行。在编译角度我们可先将你们全部变成 .o最后如何形成可执行再自己做组合 为什么要有链接步骤呢 这是因为我们要站在巨人的肩膀上。 例如我们要用到的输入输出函数要是自己来写的话那太费劲了每做一个项目都要自己先敲一个函数出来而且写出来也不够好容易出问题。因此C语言将最基本的功能给我们全部开发好再打成包这个包就是库。 解下来我们写代码时我们只需要将自己的代码编译好和C语言标准库链接形成可执行就行 有小伙伴可能会问预处理时不是已经展开头文件了吗为什么还要链接呢 预处理展开的仅仅只是声明因为头文件时公开的。 其实我们包的头文件源代码都是公开的只有声明没有实现实现在对应的同名 .c 文件里。.c 文件 C语言 没有给你暴露出来直接编成库了。 要最终形成可执行重要的是方法而链接就是将方法找到 当然上述讲的只是一般情况你要是不喜欢也可以一次就形成可执行文件
2.7 几个问题
2.7.1 如何理解条件编译 我们创建 code.c 文件写下如下代码 根据我们前面的知识我们知道此时我们并没有定义M执行的应是printf(社区版/免费版 version1\n)语句 我们执行一下看看 gcc 编译时支持我们用 「-D 」 来进行命令行级别对指定源代码进行动态添加宏 如 定义加写值 gccgcc code.c -o code.exe -DM1 只定义不写值 gccgcc code.c -o code.exe -DM gcc不用「-D 」选项定义宏它又会变成免费版 gcc这合理吗其实是合理的 gcc编译器进行编译时第一步就是预处理预处理的本质其实就是 让编译器编辑(修改)我们的代码 既然预处理时编译器能去注释能进行宏替换那么编译器将命令行中的 -DM 解释成 #define M 并将其当做字符串插入到我的代码当中不过分吧。 「-D 」相当于在命令行给代码定义宏 相信对条件编译大家都能理解大家不理解的是条件编译的用途。下面我们简单来了解一下条件编译的应用场景 对一款软件通过专业读、收费标准等进行区分使用条件编译进行代码的动态裁剪 我们平时看到的某些软件像VS、Xshell等往往都分为专业版和社区版收费版和免费版。他们两者的区别主要是在功能方面比如收费版支持100个功能而免费版只支持50个功能。 这些软件也都是程序员开发的那么程序员在维护这款软件需要维护几份源代码呢毕竟这款软件有两个版本。 事实上如果将同一款软件的免费版和收费版当成两个项目来看那么公司就需要有两套班子但其实他们功能上无非就是收费版上做一下功能的裁剪就是免费版。 所以在公司内部我们只需要维护一份源代码即可最终在发布的时候只需要告诉别人编译这个代码时编译成免费的还是收费的。怎么才能做到这点呢我们可以将软件中的功能拆分一下公共都有的放在一个模块里需要收费的放在一个模块里最后用条件编译将其维护起来。 这样一份代码通过条件编译就能对其进行裁剪从而实现对内只需维护一份源代码对外实现多份版本的目的。 Linux 内核源代码也是采用条件编译进行点裁剪 我们的 Linux 内核编译好了其实体积还是很大的但有些功能在很多的小型设备上嵌入式设备、智能家电等上面根本就不需要Linux支持那么多功能这时就可以用条件编译实现代码的动态裁剪 当然条件编译的功能远远不仅于此但大多应用场景离我们现在的水平太远感兴趣的小伙伴可以自行深入了解。 2.7.2 为什么编译器要先将代码翻译成汇编语言 C语言翻译成二进制指令相信大家都能理解因为机器只认识二进制。但为什么编译器要先将C语言翻译成汇编语言再将汇编语言翻译成二进制呢 为什么计算机只认识二进制 简单来说是因为 0 和 1 是最简单的硬件电路简单就意味着可靠计算机通过与非门各种各样的门电路组合成各种复杂电路。 这里讲一下计算机的发展史 计算机都是要进行输入输出的我们将数据喂给它它处理完后将结果返回。我们编程的本质就在在控制计算机我们编译代码其实就是在要求计算机帮我们做这做那 早期的计算机都是非常大的而且其运算力非常差。早期我们没有编程控制计算机用的是计算机上的开关早期的计算机科学家都是在计算机前掰来掰去的其实就是在通过开关来给计算机输入 0 和 1 后来人们觉得开关的方式不太好到了五六十年代人们开始用打孔编程。 打孔纸带 打了孔的地方光能透过去我们认为是1否则为0。《三体》中叶文洁向外星人发送信号时手上捏着一条纸带就是这个东西 但打孔编程本质依然是二进制编程二进制编程可是很恶心的。而且打孔打错了纸就报废了要重新打孔浪费纸张不说还效率低下。后来人们发明了一种编程语言汇编语言 用汇编语言控制计算机效率无疑比直接二进制编程高很多。 从我们的汇编语言开始就需要一个东西编译器。因为汇编语言本质上也是文本所以我们需要一种编译器将汇编语言编译成对应的二进制。 这里有个问题第一个汇编语言编译器是用什么写的呢 这个编译器要编译汇编语言那编译器自己应该用什么语言来写呢 用汇编吗你用还没法翻译成二进制的汇编来将汇编翻译成二进制这不鸡蛋和鸡吗 因此第一版编译汇编的编译器是用二进制写的。先用二进制写一个二进制版的汇编编译器。有了第一个编译器此时就可以编译汇编语言了此时我们就可以用汇编语言写一份汇编版本的编译器第一版的编译器就可以不要了此后我们就可以用自己语言写的编译器编译自己语言。这个过程叫做编译器的自举过程 不仅如此语言也是可以自举的。比如C推出C11但此时的编译器只支持C98这时就可用98写个能编11的编译器再用C11进行重写 最早期的比较好的操作系统叫 Unix它第一版本就是由肯·汤普逊用汇编语言写出来的后来丹尼斯里奇发明了 C语言肯·汤普逊和丹尼斯里奇即一起用C语言把 Unix 进行重构发布的 C语言 版本的 Unix。我什么肯·汤普逊最开始用汇编语言写因为最开始只有汇编后来 C语言 出来了C语言 对应的编译器也诞生了为了代码本身的可维护性他就把 Unix 操作系统调整为 C语言 了。 再后来人们觉得汇编语言也太麻烦所以基于汇编语言产生了许多分支编译型语言在那个阶段就开始爆发了。最典型的就是 70 年代产生的 C语言再到后来的 C/java/go 现在有了 C语言C语言 最终肯定也要翻译成二进制。现在的问题是我们是直接将C语言翻译成二进制还是先翻译成汇编语言再翻译成二进制。 我们肯定会选择方案二。为什么呢 首先将C语言翻译成汇编语言毕竟还是从文本到文本它的翻译难度相对较低其次在C语言产生之前汇编语言已经发展了很多年了我们只需要将C翻译成汇编而将汇编翻译成二进制这项工作已经发展的很成熟可以不用做了我们可以站在巨人的肩膀上。 如果直接将 C 翻译成二进制那么翻译的成本会特别高而且 C 等后来者是基于C语言发明出来的你让我 C 怎么办难道我 C 也要直接翻译成二进制吗 我们要学会站在巨人的肩膀上计算机每一阶段的发展都经过了十几年我们要将每一阶段的发展好好用上。 而编译是逆历史的过程C语言 - 汇编 - 二进制 好啦本期关于编译器 gcc/g 就介绍到这里啦希望本期博客能对你有所帮助。同时如果有错误的地方请多多指正让我们在 Linux 的学习路上一起进步