深入浅出理解计算机核心知识系列【C++语言特性合集-编译与链接篇】
本人志在持续更新计算机系统、计算机网络、C语言的核心知识点的系列合集以易懂、全面的方式讲解底层知识。对于正在准备面试八股的朋友来说本系列涵盖了本人面试中遇到的所有考点以及许多相关拓展知识读完后能帮助你从容面对大部分面试拷打对于想要深入学习计算机知识的朋友来说本系列比较系统地介绍了操作系统和网络等重点内容也举了不少例子大大有助于你从底层的视角去理解计算机系统。先说明本系列恐怕不是计算机小白或是想速通期末的朋友们的目标它需要一定系统和语言基础也并不是面向教材和考试要求去讲解所以更适合那些实操过代码、了解一些计算机系统知识、并且想要深入底层和扎实基础的朋友们去耐心学习。如果你是这样的人欢迎阅读该系列文章并分享自己的理解或提出文章中的模糊、错误的地方不排除有。想要阅读系列中其他内容或想要持续关注本系列更新可移步https://github.com/feiyangyang11/Cpp-Core-CS-Interview-Guide.git。C 编译编译主流程预处理处理#include、#define宏展开、条件编译#ifdef、去掉注释生成.i文件编译把预处理后的代码做词法分析、语法分析、语义分析生成中间表示再做优化最终生成汇编代码.s汇编把汇编代码转成机器码目标文件.o这一步基本是一一对应链接把多个目标文件和库合并成一个可执行文件或动态库。预处理本质是文本替换没有太多值得分析的地方理解编译与汇编要先学习编译原理中的词法分析、汇编语言等知识且更多属于编译器和其他底层开发者的工作在此不再深入讲述而链接与我们编写 C 代码的方式息息相关更值得我们广大应用层开发者去研究所以本文讲解将聚焦在链接这一过程当然也有部分原因是编译原理这门课实在晦涩难懂本人学的不好于是没法在此很好地解释清楚链接目的是把多个目标文件.o和库文件里的代码/数据拼成一个最终可执行文件或共享库核心机制符号解析Symbol Resolution、重定位Relocation这里举一个简单的例子后面的分析都是基于这个例子讲解并深入分析假设有两个文件 main.cpp、math.cpp// main.cppexternintg;//全局变量声明intadd(int,int);//函数声明intmain(){returnadd(g,10);}// math.cppintg5;intadd(inta,intb){returnab;}到链接这一步前这两个文件会各自生成main.o和math.o文件此时 main 中虽然知道有 g 这个变量和 add 这个函数但并不知道它们的实际地址在哪只能在此留下重定位项。而链接要解决的就是建立重定位项和实际定义之间的关系.o文件是什么.text 和 .data 等都属于进程虚拟地址逻辑分区详解参考操作系统合集-进程篇.o 是 relocatable file即可被重定位的文件。它包含很多 section大致结构如下main.o.text//本文件中定义的机器指令.data//本文件中定义的已初始化全局/静态变量.bss//本文件中定义的未初始化或零初始化全局/静态变量.rodata//本文件中定义的常量字符串等.symtab//本文件自带的符号表.rela.text//重定位表定位定义在其他代码文件中的机器指令.rela.data//重定位表定位定义在其他代码文件中的已初始化全局/静态变量...符号与符号表在进入到链接之前代码文件中g、add()这类变量 / 函数会被编译成符号g、add每个符号在符号表中有自己对应的表项表项中保存符号名类型属于哪个 section在 section 内的偏移量全局/局部是否已定义对于g变量//在 math.o 中可能是Symbol:g Section:.data Offset:0Size:4Global:yes//在 main.o 中可能是Symbol:g Section:UND//undefined当前 .o 只知道这个名字但不知道定义在哪链接器要去其他 .o 或库里找//因为只声明 extern int g;重定位表生成main.o时add()的最终地址还不知道于是该文件的.text机器码里只能暂时留一个占位值。同时.rela.text记录此处是占位值后续需要重定位重定位表只保存需要重定位的符号信息每个表项大概长这样structElf64_Rela{Elf64_Addr r_offset;// 在机器码中要修改的位置Elf64_Xword r_info;// 在本地符号表中的编号 重定位规则Elf64_Sxword r_addend;// 附加常数};r_offset表示要重定位的符号add()在机器码中的地址相对本文件 .text 的偏移量r_info保存待重定位符号在本地符号表中的编号symbol index。还保存重定位规则比如决定符号地址究竟如何计算——如果此处的机器码是call add(...)那么应该填入add实际地址相对于当前指令的地址偏移量如果此处代码类似long* p g;那么应该填入g的绝对地址r_addend是重定位计算中的附加常数用于表达符号地址的额外偏移或者适配具体机器指令的寻址规则。以 x86-64 的call rel32为例链接器最终需要在call的 4 字节位移字段中填入disp。设目标符号add的最终地址为S该位移字段的地址为P附加值为A则对应重定位可表示为disp S A - P。由于 CPU 执行call rel32时使用的是“下一条指令地址 disp”而P指向 4 字节位移字段的起始位置下一条指令地址为P 4因此需要令A -4使得disp S - (P 4)最终 CPU 恰好跳转到地址 S。用简单的话讲r_offset决定哪个地方原来填的是临时值需要重定向r_info决定要重定向的是哪个符号、要算相对地址还是绝对地址r_addend为算重定向的符号值时提供一个修正量强符号和弱符号符号有两种属性强和弱如果一个 UND 的符号引用链接时在其他文件符号表中匹配到多个同名符号定义遵循如下规则强 强冲突报错multiple definition of g重复定义强 弱选择强符号的定义弱 弱选其中一个符号的定义强符号用户代码定义的函数 / 变量弱符号显式声明为 weak或者某些编译器/链接器机制生成的可被强符号覆盖的符号链接步骤1.符号解析链接器将符号引用匹配到符号定义比如//两个文件的符号表情况main.o add → undefined g → undefined math.o add → defined g → defined//链接匹配得到main.o:add-math.o:add main.o:g-math.o:g如果未匹配到符号的定义就会报链接错误如undefined reference to add()如果出现强符号冲突就会报重定义错误如multiple definition of g链接器顺序读取每个 .o处理每个 .o 的符号表时候会把遍历到的需要跨文件解析的符号加入到全局符号表避免每遇到一个 UND 都扫描其他所有.o文件如果读到 UND 符号会检查全局符号表是否已有此符号的匹配关系有则直接利用否则加入全局符号表并标记为unresolved如果读到符号定义会检查全局符号表中是否已有此符号的匹配关系有则根据符号强弱关系决定是否更新否则在全局符号表中新添对应的g → math.o::g这样一个映射表项而对于 .o 文件自己的符号表它不会在全局符号表中匹配成功时改掉本地符号的 UND而是在本地另建立一套区别于本地符号表的上下文在其中添加本地符号 - 全局符号表表项 - 符号定义的一条路径注这里指的本地符号表不是指存放局部变量 / 函数符号的表局部变量不会参与跨文件解析而是管理本文件所有符号的表2.section 合并和地址布局进入链接之前每个 .o 文件中各自都分好了 section如main.o .text 100B .data 20B math.o .text 80B .data 16B链接器在这一步会把它们合起来变成//最终可执行文件.text ┌─────────────────┐ │ main.o.text │ │ math.o.text │ └─────────────────┘.data ┌─────────────────┐ │ main.o.data │ │ math.o.data │ └─────────────────┘//然后进行内存对齐、决定最终虚拟地址.text →0x401000.data →0x404000//直到这一步符号终于有了最终地址main 函数 →0x401000add 函数 →0x401080g →0x404010//这些结果会写入全局符号表 entry 里更新这个全局符号的最终 value3.重定位在编译main.o时由于add()地址未知所以会在自己的.text中先放一个占位值进入该阶段后链接器遍历每个 .o 文件的重定位表把要重定位的符号一个个重定位把原来写在机器码里的占位值替换成真实的地址值可能是偏移地址也可能是绝对地址根据重定位表表项配置决定详细计算方式在介绍重定位表中有讲解以某个符号的重定向为例如.rela.text记录r_offset:0x20r_info:r_Symbol:add//符号在本文件符号表中的编号r_Type:R_X86_64_PLT32 r_addend:...//遍历到上面这一项时这样计算//通过 r_Symbol - 到本文件符号表找到符号名 - 到全局符号表中找到符号真实地址 - 根据 r_Type、r_addend 计算得到最终地址这一步后机器码已经成为完全体(⊙﹏⊙)静态库与静态链接静态库.a本质上是很多.o文件打包成的归档文件。静态链接时链接器只从.a中按需加载.o文件再进行链接可以理解为静态库.a就是一坨还没有经历过符号解析、section 合并和重定位的.o文件以及一张符号索引表的集合符号索引表静态库中有一张符号索引表准确说是一套体系它由三部分组成符号个数 N数组 AN 个整型保存该符号所在的.o文件头在整个.a文件中的字节偏移数组 BN 个以\0为结尾的字符串即符号名成员顺序和数组 A 一一对应这张表用来做什么主要是为了避免加载整个静态库做到按需加载当外部要链接该静态库时会先做符号解析。查到 UND 符号时如果想要搜索静态库直接遍历数组 B 比对符号名。如果查到对应符号就找到数组 A 中相同索引的元素获取.o文件的地址将它纳入本次链接即所谓按需加载静态库如何组成的静态库就是先把一堆.o打包在一起然后遍历到所有可跨文件访问的符号建立符号索引表组成了.a文件静态库链接顺序很重要# -lxxx 表示静态链接 xxx.agcc-lmathmain.o gcc main.o-lmath这两种写法可能造成不一样的链接结果因为链接器通常从左到右处理输入。如果解析main.o中标记add为 UND然后在解析math.a时发现add可以在符号索引表中查到那么就会将对应.o文件从math.a中抽出并参与链接如果链接顺序反过来那么main.o就找不到add的定义了会报错所以链接顺序要做到依赖别人的对象放前面被依赖的静态库放后面静态库循环依赖链接了两个静态库 libA、libB彼此都有 UND 符号在对方之中——解析 libA 时产生B1 UND然后去 libB 中查出 B1又产生 A2 UND但 libA 已经过去拥有 A2 的.o没有被链接导致 A2 链接失败解决方法让链接器反复扫描这一组静态库直到没有新的.o需要抽取更建议的做法是减少这种循环依赖写法动态库与动态链接动态库.soWindows 上是.dll和静态库最本质的区别是静态链接把代码复制进可执行文件动态链接则是运行时才把代码加载进来、让多个程序共享同一份正因为不是链接时一次性拷贝动态链接要额外解决两个静态链接没有的问题代码加载到内存的哪个地址是运行时才决定的编译期不知道所以机器码里那些引用全局变量、调用函数的地址没法在链接时填死函数什么时候才真正解析出地址如果程序启动就全部解析启动会很慢下面按「地址怎么解决」和「解析时机怎么优化」两条线讲1.位置无关代码PIC静态链接时每个符号的最终地址在链接阶段就定了所以机器码里可以直接写绝对地址。但动态库是共享的可能被加载到任意地址如果代码里写死了绝对地址一换加载地址就全错解决办法是编译动态库时加-fPIC生成位置无关代码代码段里不出现绝对地址凡是引用全局变量、调用外部函数的地方都改成「先查表、再跳转」这个表就是下面要讲的 GOT。2.GOT 和 PLTGOT全局偏移表可以理解成一张「地址表」每一项存一个符号的真实地址。代码引用全局变量g时不直接写g的地址而是去 GOT 里查g那一项拿到地址再访问。这样只要运行时把 GOT 表项填对代码段本身就不用改天然支持被加载到任意位置。PLT过程链接表用来配合「延迟绑定」实现用到才解析。程序启动时不解析所有函数的地址而是给每个外部函数在 PLT 里留一个桩stub。第一次调用add()时跳到 PLT 里的addplt桩桩先去 GOT 查发现还没解析过就调用动态链接器去把add的真实地址找出来填回 GOT再跳到真正的add执行之后第二次再调用add()GOT 里已经有地址了直接跳就行不再走动态链接器。这就是「延迟绑定」好处是没被用到的函数根本不会去解析启动更快3.动态链接器的工作流程.so里同样有自己的符号表和重定位表。当程序启动时内核加载可执行文件后会先交给动态链接器ld.so它做这么几件事加载程序依赖的所有.so进内存记录在可执行文件的动态段里做符号解析把可执行文件和各个.so里的 UND 符号在彼此之间匹配上定义做重定位因为编译动态库时地址未知所以重定位表里留的项要在这个阶段真正填值——结合 GOT/PLT把符号真实地址写进 GOT 表项这一步做完程序才开始执行main

相关新闻

图表生成Skill实战:从会画到画得稳的工程化升级

图表生成Skill实战:从会画到画得稳的工程化升级

如果你最近在折腾 Claude Code、Codex 这类编程智能体,应该会注意到一个词开始频繁出现:skill。它既不是新语言,也不是新框架,而是一种把 AI 的某项能力按任务封装成可复用包的做法。我把“图表生成”做成了这样一个 skill&#x…

2026/8/29 3:59:33 阅读更多 →
C++容器深度解析:从底层原理到实战选型指南

C++容器深度解析:从底层原理到实战选型指南

1. 项目概述:为什么我们需要“一文读懂”C容器?在C的日常开发中,无论是处理海量数据、管理复杂对象,还是实现高效算法,我们几乎无时无刻不在与“容器”打交道。但很多开发者,尤其是从C语言转过来或初学C的朋…

2026/8/29 3:59:33 阅读更多 →
AI代码生成进入合规时代:OpenJDK限制与Cursor收购背后的开发者指南

AI代码生成进入合规时代:OpenJDK限制与Cursor收购背后的开发者指南

这期 AI 日报里有两条消息,放在一起看特别有意思。一是甲骨文对 OpenJDK 提交 AI 代码新增限制,二是 SpaceX 月底完成收购 Cursor。单看其实是两条商业新闻,合在一起就是一个很明确的信号:AI 代码生成正在从“能跑就行”进入“合规…

2026/8/29 3:58:33 阅读更多 →

最新新闻

自托管开源NVR VitCam:本地录像与设备端AI检测实践

自托管开源NVR VitCam:本地录像与设备端AI检测实践

这次我们来看一个自托管开源 NVR 项目:VitCam。它的定位很直接:用你自己手里的服务器或 NAS,加上普通 IP 摄像头,搭一套录像、预览、AI 检测全部在本地完成的开源网络视频录像机。核心关键词就三个:Self-hosted、Open-…

2026/8/29 6:16:01 阅读更多 →
2027赛逸展聚焦机器人后市场服务,完善全生命周期服务体系

2027赛逸展聚焦机器人后市场服务,完善全生命周期服务体系

2027赛逸展聚焦机器人后市场服务,完善全生命周期服务体系。2027亚洲消费电子展(赛逸展)举办时间2027年6月26‑28日,北京亦创国际会展中心,整体规模3.5万㎡,395家科创企业参展,预计汇聚2.6万名付…

2026/8/29 6:16:01 阅读更多 →
Windows Docker 环境搭建 GitLab CI/CD 流水线

Windows Docker 环境搭建 GitLab CI/CD 流水线

Windows Docker 环境搭建 GitLab CI/CD 流水线:从零到一的完整实践 引言 GitLab CI/CD 是持续集成与持续部署工具之一。它天然集成在 GitLab 代码仓库中,无需额外搭建 Jenkins 等第三方 CI 平台,开箱即用。本文将记录在 Windows 环境下使用…

2026/8/29 6:16:01 阅读更多 →
360CDN发布元宇宙与VR/AR加速方案:边缘渲染与超低延迟流媒体,彻底终结眩晕感

360CDN发布元宇宙与VR/AR加速方案:边缘渲染与超低延迟流媒体,彻底终结眩晕感

2026年8月23日,针对当前元宇宙与VR/AR应用中普遍存在的“云端渲染延迟高导致用户眩晕”及“全景视频传输带宽成本高昂”等核心痛点,国内领先的云安全服务商360CDN正式推出“元宇宙与XR(扩展现实)超低延迟加速解决方案”。该方案依…

2026/8/29 6:16:01 阅读更多 →
留学生求职服务机构可信度评估研究 ——基于可验证资质的实证分析

留学生求职服务机构可信度评估研究 ——基于可验证资质的实证分析

报告日期:2026年8月 研究对象:留学生求职服务机构可信度评估 报告目的:为留学生提供基于可验证事实的机构选择参考框架 摘要 求职服务机构的选择,本质上是一个信任决策问题。在市场信息高度不对称的背景下,机构的“可信…

2026/8/29 6:16:01 阅读更多 →
蓝桥杯国赛A组题解:算法竞赛核心考点与实战策略

蓝桥杯国赛A组题解:算法竞赛核心考点与实战策略

1. 赛题概览与核心价值解析又到了一年一度的蓝桥杯国赛季,对于每一位奋战在算法竞赛一线的C/C选手来说,拿到国赛A组的题面,就像战士拿到了最终的作战地图。2020年第十一届蓝桥杯国赛A组的题目,在当年引起了广泛的讨论,…

2026/8/29 6:15:01 阅读更多 →

日新闻

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

一、设置环境变量etc目录下的profile.d文件目录 /etc/profile.d1、编写 vi test.sh文件内容# jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile 命令使…

2026/8/29 0:00:24 阅读更多 →
【JavaScript】内存管理-垃圾回收机制-内存泄露

【JavaScript】内存管理-垃圾回收机制-内存泄露

内存管理 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。 而 JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。 整…

2026/8/29 0:00:24 阅读更多 →
Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP 的目标是把 MCP(Model Context Protocol)能力延伸到真实嵌入式硬件实验室:AI Agent 通过一个标准化的 MCP Server,就能查看目标板状态、控制上电断电、复位开发板、读取串口日志,甚至执行镜像刷写。对于经…

2026/8/29 0:00:24 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/28 11:23:26 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/28 23:05:07 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/28 19:47:53 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/29 4:34:53 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/28 17:43:04 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/29 2:05:18 阅读更多 →