尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
编译链接的过程发生了什么?
目录摘要一翻译环境和执行环境二翻译环境工作图三翻译环境工作的展示第一阶段预编译第二阶段编译第三阶段汇编第四阶段链接四链接的功能讲解五模版函数的分离编译摘要文讲解 C 程序的翻译与执行过程包括翻译环境与执行环境、翻译环境工作图以及预编译、编译、汇编、链接四个阶段的具体工作。文章还专门讲解了链接的功能并深入分析了模板函数的分离编译问题——为什么模板声明与定义分离会导致链接错误以及如何解决。一翻译环境和执行环境在ANSI C的任何一种实现中存在两个不同的环境。第1种是翻译环境在这个环境中源代码被转换为可执⾏的机器指令⼆进制指令。第2种是执⾏环境它⽤于实际执⾏代码。也就是说↓1翻译环境代码-二进制指令2执行环境执行二进制指令❓️我们写的代码是文本信息而计算机执行的是二进制的指令这二者之间如何进行的转换换句话说就是翻译环境中发生了什么才让代码-二进制指令如下图解释我们写代码的文件叫作.c源文件会经过编译器生成目标文件不同环境和编译器的目标文件不同比如VS是.obj,Linux环境下的gcc是.o最后通过链接器把生成的目标文件和链接库进行链接生成可执行程序。二翻译环境工作图VS上从写代码到生成可执行程序的过程编译器隐式得为我们做了太多事情才让我们能从.c(源文件)一下到.obj(目标文件)所以为了逐步展示翻译环境中的细节博主选择在Linux环境下的gcc编译器下进行细节上的演示~翻译环境分为四步①预编译也叫作预处理②编译③汇编④链接三翻译环境工作的展示前提准备源文件创建及代码书写我们在gcc中创建一个class110目录在其下创建一个源文件test.c在里面写上如图所示的代码第一阶段预编译首先在终端对我们上面创建的test.c这个源文件进行gcc -E -o test.igcc -E -o test.i的意义执行完预编译阶段就停止然后把内容放在test.i这个文件此时生成了test.i这个文件test.i文件内容如下将test.i和test.c进行对比解释将test.i和test.c进行对比我们就能发现了第一阶段预编译的作用①注释的删除②头文件的包含test.i前面的800行就是#includestdio.h的内容③#define符号的替换直接把M替换成了100④综上所示整个翻译环境是不对.h文件进行处理.h文件是在被包含的源文件进行到预编译这一步时才会去展开总结所有的预处理指令如文件包含、宏定义、条件编译等都是在预编译阶段完成的这些统称为文本操作第二阶段编译解释①首先进行gcc -S test.igcc -S test.i的意义对test.i执行完第二阶段编译就停止内容放进自动生成的test.s中②此时打开test.s发现全是汇编代码 所以编译阶段的工作是将代码翻译成汇编代码放进了自动生成的test.s中第三阶段汇编首先进行gcc -c test.cgcc -c test.c意义为test.c执行完第三阶段汇编就停止放进自动生成的目标文件test.o中然后试图打开test.o被警告因为test.o是二进制文件无法被直接打开所以汇编阶段的功能将汇编指令翻译成了二进制指令强行打开目标文件test.o注ELF是一种文件的格式 需要用指令readelf来读取ELF格式的文件所以我们用指令readelf来读取ELF格式的文件此时发现我们需要输入一些选项-a,-h,-l.......来进行选择性的查看我们使用-a选项即可查看到段表如下解释这就是段表也就是说ELF这种文件格式其实是按照一个一个的段来存储的这也是第四阶段链接中的功能合并段表中所谓的段表也可以-s来查看符号表解释①此时发现符号表中有我们代码的全局变量g_val函数Add函数main库函数printf②这些都被存储到了符号表中 这就是第三阶段汇编的另一个作用形成符号表那么符号表是做什么的在第四阶段里面有作用第四阶段链接如上进行gcc test.o -0 test 即可链接生成可执行程序test然后尝试打开可执行程序test但无法打开因为可执行程序test经过链接之后依旧还是二进制文件所以要想打开用ELF即可四链接的功能讲解假设现在有两个.c源文件解释一个.c只是声明了Add函数一个.c有函数的定义所以链接的功能如下图解释①每个源文件都在编译阶段进行了符号汇总在汇编阶段进行了形成符号表即函数和对应的地址进行合并形成符号表②但是对于test.c中这种仅有声明的函数我们将其地址暂且存为0x0000等到链接的时候与其他的.obj或.0目标文件(.c文件经过预编译编译汇编得到的目标文件)的符号表进行所以就在add.c对应的add.o目标文件中找到了add函数的确切地址完善了add函数对应的符号③Add的地址确认为有效地址add.c中的函数地址就叫作符号表的重定位所以经过链接这一步才能使用函数定义在另一个文件的函数还有其他库中的函数也需要链接这一步才能找到五模版函数的分离编译链接的本质就是通过符号表的合并和符号表的重定义得到各种函数真实的地址从而才能让一个源文件中所涉及到的函数被找到调用❓️但是了解过C模版函数的朋友都知道模版函数是不支持声明(.h)和定义(.cpp)分开文件放的为什么呢解释如下普通函数不管你是否声明定义分离起码最后通过链接就可以得到函数的准确地址但是模版函数即使通过了链接也得不到模版函数的准确地址因为模版函数需要用户给他传递模版参数其才可以实例化出具体的函数才可以生成具体的函数地址模板函数如果没有被实例化编译期就不会生成该模版函数对应的符号于是链接器根本找不到这个符号也就得不到对应的函数地址❓️我们的main中不是在调用模版函数时传递了具体参数吗为什么不会实例化呢前文中的Add.c文件中有着Add函数的定义此时编译器检测到该文件存在非模版函数Add编译器就会无条件为Add函数生成对应符号但是若Add.存放的是模版函数则编译器会无视掉不为其生成对应符号这就是为什么模版函数在经过链接之后仍然找不到函数地址这和你在main中调用模版函数时传递了具体参数无关❓️为什么编译器要这么做呢像对待普通函数一样对待模版函数不行吗因为如果编译器这么做了假设现在有100个文件调用了该模版函数则编译器需要读取100个文件才知道每个文件调用该模板函数时传递的模版类型是什么然后再去Add.c对其中的模板函数分别传递100次类型从而实例化出对应函数生成函数地址太麻烦了影响效率所以编译器不这么做C直接规定模版函数的声明定义不能分离必须写在同一个文件中使用但总有情况使得模版函数不易分离此时还有另一种办法文件 1func.h#pragma once templateclass T T Add(const T a, const T b); // 只有声明文件 2func.cpp#include func.h templateclass T T Add(const T a, const T b) // 定义 { return a b; } // 显式实例化只生成 int 版 template int Addint(const int, const int);文件 3main.cpp#include iostream #include func.h using namespace std; int main() { cout Add(1, 2) endl; // ① Addint → func.cpp 已实例化 → ✅ cout Add(1.5, 2.5) endl; // ② Adddouble → func.cpp 没实例化 → ❌ return 0; }解释①在模版函数定义的文件中进行显式实例化比如让其只能实例化为int其实就是将这个模版函数伪造成了一个类型为int的非模板函数②缺点就在于下次你想传递模版参数为double调用该模板函数就不适用了需要手动将显式实例化的代码改成double这就是显式实例化的硬伤。所以模版函数的声明定义放在一起才是最正确最简单最严谨的做法 [ 作者 ] shylyly [ 首次发布 ] 2024.10.8❌ [ 最新修改 ] 2026.9.28 [ 声明 ] 由于笔者水平有限文中难免有疏漏或不妥之处还望读者不吝赐教
RELATED

相关推荐

MCP协议的定位类似USB-C,但有其适用范围

MCP协议的定位类似USB-C,但有其适用范围

上周在一个技术群里,有人扔出来一个问题,MCP到底是AI界的USB-C,还是又一个炒作。群里安静了三秒,然后炸出两拨人,一拨开始贴数据,一拨开始贴翻车案例,从下午吵到晚上,消息刷了一百多…

📅 2026/9/29 2:19:20
如何快速找到开源游戏:open-source-games 清单新手完整指南

如何快速找到开源游戏:open-source-games 清单新手完整指南

如何快速找到开源游戏:open-source-games 清单新手完整指南 【免费下载链接】open-source-games A list of open source games. 项目地址: https://gitcode.com/GitHub_Trending/op/open-source-games open-source-games 是一个单文件开源游戏清单&#xff1…

📅 2026/9/29 2:19:20
ECRS软件标准工时制定:提升企业生产效率的关键路径

ECRS软件标准工时制定:提升企业生产效率的关键路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/29 2:19:20
MORE NEWS

更多资讯

📰

TensorFlow 2024生存指南:安装部署、生态对比与实战路线

TensorFlow在2024年到底是什么处境,还有没有必要从零开始学,这个问题我几乎每天都能看到有人在讨论。先说结论:TensorFlow依然是工程化和生产部署领域绕不开的主力框架,而且在移动端、嵌入式设备上它有明显的生态壁垒。但如果你是…

📰

阿里云部署OneApi:用Docker跑通TaoToken统一Key通道的配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

LLM Agent记忆系统实战:Hindsight架构、MCP工具链与Docker部署

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”“hindsight”这个词,直译过来就是“后见之明”。放在LLM Agent的语境里,它指向一个非常具体且要命的问题:Agent的记忆到底该怎么管。我接触过不少做Agent落地的团队&…

📰

Conda与pip环境管理实战:从环境隔离到镜像源配置与报错排查

用Conda和pip创建Python环境,这事儿听起来像Python开发里最基础的操作,可我实际收到的求助里,十有七八都是卡在这一步。不是不会敲conda create和pip install,而是搞不清这两个工具的分工,更不知道装完之后还要初始化、…

📰

ArcGIS避坑指南:从安装许可到空间分析的高频问题排查手册

做GIS这些年,我见过太多人不是倒在复杂分析上,而是被安装、坐标系、数据转换这些“小事”卡到怀疑人生。ArcGIS这套东西功能确实强,但老牌大型软件的脾气也一点不少:安装讲究顺序、环境挑剔路径、坐标系一步错步步错。之前我在零散…

📰

记忆张量亿元Pre A融资后,AI记忆如何成为Agent的长期基础设施

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬