【跟着操作就行了】手把手教你 编译+链接 程序环境教程

简介: 【跟着操作就行了】手把手教你 编译+链接 程序环境教程

程序的翻译环境和执行环境


在ANSI C的任何一种实现中,存在着两种不同的环境。


也就是说,我们写出C语言的代码想编译成可以运行的程序,就必须要经过这两种环境。


第一种是翻译环境,在这一环境中源代码被翻译成可执行的机器指令。


另一种是编译环境,用来实际执行代码。


翻译成图大概就是这个样子。


e1ca73d076d1c8d95db1c9d1c153c9f0_05e0bbe1a8184aaba6305963ab719f4f.png


其中,test.exe当然就是可执行的二进制指令,也被称为上面所说的机器指令。


详解编译+链接


翻译环境


我们知道,一个程序可以有许多源文件。编译器可以将这众多的源文件转换为目标代码。


而要把这众多的目标文件链接到一起,就需要链接器(linker)来大展身手了!


链接器可以将众多的目标文件捆绑到一起,形成一个单一而完整的可执行程序。


我们可以搜索到编译器和链接器。


63cc7eeb719fdb649d1627848b78db60_a39b1c9e470b4caea61d1244da2ae4c3.png

07bf3b86dbcd5a918b1ca4e927e43879_20ee744b3f994130b6dc98db7a7ff18b.png



图一是编译器,图二是链接器。


链接器同时也会引入标准C函数库中被该程序所用到的函数,它还可以搜索程序员个人 的程序库,并且将其需要的函数也链接到程序中。


我们想观察到编译和链接的过程,就需要用到gcc编译器,为什么不能用VS呢?


因为VS是一个集成开发环境,集成很多功能,一个ctrl+f5直接出结果了,不方便观察编译和链接细节。


编译本身也分为几个阶段


预编译(预处理)


翻译环境要经过编译和链接两个阶段,编译的过程又分为预编译,编译和汇编。


画成图的话就是下面这样(我 的 肝 好 累)(;´༎ຶД༎ຶ`)


390fe689f72b21066e6d5cf0c4a8f56c_7d4b437427174923927584ebbd1c7576.png


我们先在gcc编译器搭建好环境。


266b47116bd098789d300b7eccfd2f79_1f0be656b8fd43538c10590352216b29.png


我们想让test.c文件只是预编译,而不执行后面的操作,那就要这么写命令行。


gcc  test.c -E -o test.i


9a80b19a298f0559ea2ee5c17d3e02d4_e4a179a86d064a178abc50debc187ea0.png

9d4003ea31a6e5d0c8085ce757573eac_bcfe667407c748ac9d73b1763e900516.png


这样我们就将预编译的内容写到了一个叫test.i的文件里面。


只写gcc test.c -E -o的话,上面那一长串代码就会打印到终端上面。


f3274e64e682e0b3513f2c5c054e250d_c04369c514ed4632aa200421ccb82740.png


我们在这一串代码中发现了老朋友头文件stdio.h的身影。


那就引出了预编译的第一个功能——头文件的包含。


01a20a4469a34e6d94260ec00d938dbd_ad383f309805453e8615eeefc9de631c.png


上面是test.i文件里存放的预处理之后的代码。我们发现,SZ被替换成了10,也是它被定义的值。


我们就发现了预编译的第二个功能——#define所定义的符号的替换。


预编译的第三个功能就直接写出了——注释的删除


上面的三个文本操作确实会让代码的行数减少很多。


编译


要观察编译的过程,我们写出这样的命令行。


a86f5bd2a13f152ee20dae950b2efa51_d34b2e5271ae4a5aa2b5eaf4b34d4ffe.png


运行命令,发现跳出一个叫test.s的文件。


这个文件里的内容和汇编指令十分相似。


1346f3ee2db2a532637815936ef127ec_3b945067452f4dc9a25d01dc5253d8c4.png


所以编译阶段所做的事情就是把C语言代码翻译成汇编代码。


当然这之间的事情十分的复杂,要经过语法分析,词法分析,语义分析,符号汇总的操作。


大家可以去购买相关的书籍查阅,像《程序员的自我修养》里面就有相关的解析。


其中的符号汇总我下面会讲。


汇编


我们接着写出这样的命令行  gcc test.s -c。


08b847152ac7402ebda4023f32a4b321_9d576b9305fa4b1ab3d6487fbecabc46.png


弹出一个 test.o 文件。


在VS编译器上面,目标文件的后缀是.obj,而在gcc编译器,目标文件的后缀是.o。


所以test.o这个文件就是目标文件了。


目标文件是什么格式呢。


e28d4929f3db8dea6471410cd6e27dad_90e4a4799fd64ddd8f80cf3ae7c311ae.png


我们点开这个文件,发现它是二进制文件。


我们就明白了,汇编所做的事情就是将编译出来的汇编代码转换成二进制指令。


链接


链接过程就是将众多上面的test.o文件编译成test.exe,也就是可执行程序。


输入gcc test.o来直接编译test.o文件。


61de117fb51fb18d8569b8b48bf86868_bfa2589bcf404156b72311dcea84e0fa.png


生成一个a.exe文件。


直接运行a.exe文件。


87b8e2a3d86c50ac2c1896cf463f46f3_46a02b9aaf704a0a8e7d27b02743ec16.png


同样可以达到test.exe的效果。


上面说了, 链接过程就是将众多上面的test.o文件编译成test.exe,也就是可执行程序。


那如何编译呢?要经过两个过程:


1.合并段表


2.符号表的合并和重定位。


我就以下面的代码为例来讲解。


01d563bbbcdeba76263602c22e6c7b34_1234d09da5754d948816f5e36634a7ef.png


我们已经知道,上面的代码经过汇编会产生两个 .o 文件,我们就假设一个为Sub.o,另一个为test.o。


什么是符号汇总?


在编译阶段,程序会进行符号汇总的操作。


将函数声明和全局变量等进行汇总,其实就是收集起来。如图所示。


cdac46b0cecb696841ae6a3e9556d12c_2edd346df82f45c59c62058d4319df07.png


当完成符号汇总操作之后,程序就该进入汇编阶段了。


汇编阶段就是将编译出来的汇编代码转换成二进制指令。


这里面就包含了形成符号表的操作。


805d1d70922c17485d57ad325f03d05e_0d87b0fd3af64043a85bc41ab198b011.png


我们给收集来的符号假定几个地址,其中test.o里面的Sub给上空指针。


接下来就该合并段表和符号表的合并和重定位了。


合并段表



6576acbc1d2f2f00a39f99208c0b9a03_9f471ea725d042b1a442d213cc87550d.png


各目标文件需要链接库来形成可执行程序。


目标文件,已经是二进制的文件。它们是有格式的。


就以gcc编译产生的目标文件为例,目标文件的格式是elf的文件格式。


可支持程序文件的格式也是elf的。


这种文件格式会把文件分成段。


编译器就把这对应段的文件合并在一起,形成可执行程序。


479083458eee907126d677d909b92184_704f7496a0764d44ad666bdd522f8b7b.png


当然,这一块的操作是非常复杂的。


符号表的合并和重定位


Sub.o文件和test.o文件各有各的符号表,难道生成的文件里也是各有各的文件表吗?


当然不是,它们要进行符号表的合并和重定位操作。


b0392859d3593138e4e7e9dbea827636_69427d9a5cd8473cb3db1d49848bc5bc.png


无效的地址会被清除,从而形成一个新的符号表。


它有什么用呢?


当我们删除声明的Sub函数,读取的符号表会变成main函数和空指针的Sub函数声明


我们通过这个空指针函数声明去找未定义的Sub函数时,就会报出链接时错误——无法解析的外部符号。

0cf03a9cbbd68535ebf55d9e883a4147_c927538ed0674b0884f9e9174b00a980.png

a1d3e9b8e8b36193de2109fca1d0d656_67b36a7d62c24afab00f9c8211eda3db.png


如果函数的名字写错也是同理。


总结

 感谢观看,本文到这里就结束了,如果觉得有帮助,请给文章点个赞吧,让更多的人看到。🌹 🌹 🌹


cad3f4971ac28288f5f73c67c1f7b77b_7673ea0a00c3431893891e0c2913a10e.jpeg


 也欢迎你,关注我。👍 👍 👍


 原创不易,还希望各位大佬支持一下,你们的点赞、收藏和留言对我真的很重要!!!💕 💕 💕 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!下期再见。🎉


相关文章
|
7月前
|
数据可视化 测试技术 API
阅读源码有哪些好方式与好步骤
阅读源码是理解软件工作原理的关键。首先,了解背景、目的和技术栈。从文件头部的文档注释开始,逐步深入到复杂代码。利用Git、调试器和分析工具辅助理解。保持批判性思维,质疑代码设计并验证理解。拆分代码块,画图展示结构,使用版本控制追踪变更。搜索、阅读文档、API和单元测试以深化理解。参与讨论,做笔记,回顾历史版本,了解上下文,并通过实践加强领悟。每个人的方法可能不同,关键是持续学习和适应。
66 1
|
7月前
|
文字识别 NoSQL 物联网
分享55个C源码源代码总有一个是你想要的
分享55个C源码源代码总有一个是你想要的
79 1
|
7月前
|
JSON 编译器 C语言
VScode配置C语言环境 亲测 可用!!!
VScode配置C语言环境 亲测 可用!!!
|
JSON 小程序 JavaScript
微信小程序学习第一周的第一篇博客(知识点:了解什么是小程序、各文件的作用和小程序的宿主环境)
微信小程序学习第一周的第一篇博客(知识点:了解什么是小程序、各文件的作用和小程序的宿主环境)
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(2)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。
|
编译器 C语言
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(1)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。 1.1程序翻译中的的编译和链接
|
Linux API 开发工具
不知道如何看Android源码?试试这几种方式~
Android这个是一个**庞大的系统性**的工程,各个版本都有一定兼容性问题,为了能快速定位问题,也为了学习Android框架中一些优秀的思想,时常需要查看Android系统源码层面的知识。
|
Oracle IDE Java
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
427 0
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
|
XML IDE Java
阅读Spring源码第一步:源码编译与创建调试入口
 Spring开源框架经过很长时间的发展,各个模块均已成熟,一个常识就是一个可靠,可扩展的高性能框架,它的代码行数是相当可观的,我用static插件简略测算了一下,Spring的源码有100多万行,可以想象其中的调用逻辑是相当复杂的,所以将Spring源码下载到本地再编译的话,我们就可以通过IDE的debug来来到抽丝剥茧分析源码的目的,并且我们可以很方便的使用idea来查看调用栈,方法的调用关系也就比较明了了。
阅读Spring源码第一步:源码编译与创建调试入口
下一篇
DataWorks