公司动态

Linux基础开发工具(五):理解链接与库——从原理到实战

📅 2026/8/1 16:41:34
Linux基础开发工具(五):理解链接与库——从原理到实战
目录前言一、C语言程序的翻译全过程1.1 翻译的四个阶段详解二、为什么 C/C 必须经过汇编2.1 早期编程开关与打孔纸带2.2 CPU 指令集与汇编语言的诞生2.3 历史的逆向路径三、核心概念编译器与语言的“自举”3.1 第一个编译器是怎么来的3.2 GCC 是用什么语言写的三、理解链接与库程序员的“共享经济”3.1 常规编译习惯与 GCC 的本质3.1.1 从源码到可执行程序的流水线3.1.2 链接的必要性3.2 为什么要有库——程序员之间的“浪漫协作”3.3 库在哪里——动态库与静态库的区别3.3.1 查看系统库文件3.3.2 感性理解动态链接 vs 静态链接3.3.2.1 动态链接共享经济模式3.3.2.2 静态链接私有财产模式3.4 实战演示默认动态链接与强制静态链接3.4.1 准备测试代码3.4.2 默认情况动态链接3.4.3 强制静态链接3.4.4 C 的情况3.5 避坑指南静态链接报错怎么办结语 云泽Q个人主页 专栏传送入口: 《C语言》《数据结构》《C》《Linux》《蓝桥杯系列》《笔试算法》《AI赋能》《STM32》⛺️遇见安然遇见你不负代码不负卿~前言大家好啊我是云泽Q欢迎阅读我的文章一名热爱计算机技术的在校大学生喜欢在课余时间做一些计算机技术的总结性文章希望我的文章能为你解答困惑~一、C语言程序的翻译全过程我们在前面的文章提到了程序的翻译过程主要分为四个步骤预处理、编译、汇编、链接。这个过程就像是把一段“不干净”的 C 语言代码一步步打磨成机器能直接运行的二进制文件。1.1 翻译的四个阶段详解简单回顾一下不然感觉直接讲生态有些奇怪预处理这一步主要是把“不干净”的 C 语言变成“干净”的 C 语言。比如处理宏定义、头文件包含等让代码变得纯粹。编译把我们对应的 C 语言翻译成汇编语言。这是从高级语言向低级语言跨越的关键一步。汇编把汇编语言编译成我们对应的二进制文件。这个文件叫做可重定位目标二进制文件也简称为目标文件后缀通常是.o。链接最后将目标文件与库文件链接生成最终的可执行程序。所以整个流程就是C语言-预处理-干净的C语言-编译-汇编语言-汇编-可重定位目标二进制文件(.o)-链接-最终的二进制可执行文件。二、为什么 C/C 必须经过汇编大家平时在用 VsCode 或者 Visual Studio 写代码的时候可能会发现 C 或者 C 在翻译的时候必须是先翻译成汇编然后再变成二进制它能不能直接从 C 语言跳到二进制要回答这个问题我们需要聊聊编程语言和计算机发展的历史。这不仅仅是个历史故事它还对“自举”这个核心概念的理解。2.1 早期编程开关与打孔纸带大家可能听说过在早期计算机刚出来的时候科学家控制计算机的方式非常原始——靠拨动一大堆物理开关。通过打开或闭合开关形成一段二进制信息传给计算机。无论计算机发展到今天多么先进有一个点永远没变CPU 只认识二进制指令。后来觉得开关太累了就出现了打孔纸带。就像电影《三体》里叶文洁用的那种设备通过纸带上有孔和无孔来表示 0 和 1。这本质上还是二进制只是存储介质变了。2.2 CPU 指令集与汇编语言的诞生既然 CPU 只认二进制那程序员就得记那一堆0101的代码。CPU 生产时在内部通过光刻机固化了硬件级的指令集比如加法、减法、入栈、出栈等操作都有对应的二进制编码。但这太难记了也太容易写错。于是人们想了个办法用英文助记符来代替这些二进制码。比如用add代表加法指令用sub代表减法。这就是汇编语言的由来。本质汇编语言是对 CPU 指令集的人类可读封装。工具汇编语言需要通过汇编器Assembler翻译回二进制才能被 CPU 执行。虽然汇编比二进制好写多了但它依然很繁琐而且跟硬件绑定太死。为了解放生产力更高级的语言如 C 语言应运而生。2.3 历史的逆向路径既然 C 语言是后来才有的而汇编器和二进制转换技术早就成熟了那么最顺理成章的设计思路就是利用现有的成熟工具汇编器来解决新问题C 语言转二进制。如果直接让 C 语言编译器去生成二进制难度极大且没必要。不如先把 C 语言翻译成汇编再交给成熟的汇编器去处理。这就是为什么我们今天看到的编译流程是“逆历史”的先转汇编再由汇编器生成二进制。三、核心概念编译器与语言的“自举”这里我们要引出一个非常重要的知识点也是面试中经常会被问到的问题既然C的编译器是用C写的那么到底是先有语言还是先有编译器答案其实藏在**“自举”**Bootstrapping这个过程中。3.1 第一个编译器是怎么来的这就好比“鸡生蛋蛋生鸡”的问题。我们来推演一下 C 语言和 GCC 编译器的诞生过程第一步无中生有最开始没有 C 语言编译器。科学家们只能用汇编语言甚至直接用二进制硬生生写出了第一代 C 语言编译器。最早是用二进制写了第一版的汇编编译器有了汇编编译器后再用汇编写了第一版的 C 编译器。第二步自我复制有了第一代编译器它是用汇编写的但能看懂 C 语言我们就可以用 C 语言去写一个功能更强大的第二代编译器。第三步编译升级用第一代编译器把第二代编译器的 C 语言源代码编译成可执行程序。第四步迭代发展从此以后C 语言和 C 语言写的编译器就形成了“双螺旋式”的发展就像左脚踩右脚。C 语言出了新特性就用旧版本的编译器去编译支持新特性的新版本编译器。这个过程我们就叫做编译器的自举。3.2 GCC 是用什么语言写的基于上面的原理我们可以得出一个结论GCC 本身就是用 C 语言写的。如果你不信我们可以现场验证一下。在 Linux 终端中我们可以通过以下命令来看看 GCC 到底依赖什么库从而推断它的“血统”。[yunzeiZuf6bvbodyqq8qxjtf7l6Z lesson10]$whichgcc /usr/bin/gcc[yunzeiZuf6bvbodyqq8qxjtf7l6Z lesson10]$ ldd /usr/bin/gcc linux-vdso.so.1(0x00007ffe80fb9000)libm.so.6/lib64/libm.so.6(0x00007f8c9c896000)libc.so.6/lib64/libc.so.6(0x00007f8c9c4c8000)/lib64/ld-linux-x86-64.so.2(0x00007f8c9cb98000)通过ldd命令查看/usr/bin/gcc的动态链接库我们可以看到它主要依赖libcC 标准库和libm数学库。这有力地证明了 GCC 是一个标准的 C 语言程序。所以最终的结论是先有语言的设计才有编译器的实现而编译器的完善又反过来推动了语言的发展。这就是编译器自举的奥秘。三、理解链接与库程序员的“共享经济”接下来进入下一个核心话题——链接。该篇文章我们要初步建立起对链接和库的理解。虽然关于库的制作原理后面会开专题详细讲现有的铺垫解释不清楚但目前必须先从理论上把“为什么要用库”以及“库到底是什么”交代清楚。3.1 常规编译习惯与 GCC 的本质3.1.1 从源码到可执行程序的流水线在常规的 Linux 开发中比较好的编译习惯是永远把你的所有源文件.c统一经过编译器编译变成目标文件.o最后再把所有的.o经过链接形成对应的可执行程序。在这个过程中虽然我们口头上常说“编译器”但实际上现代编译器如gcc已经发展成了一个庞大的工具集。它内部包含了预处理器、编译器、汇编器甚至链接器的能力。所以当我们说“编译”时其实是指调用这个工具集完成了一系列工作。3.1.2 链接的必要性当我们把程序翻译成.o文件之后我们的工作还没结束。这些.o文件必须和一块叫作**“库”**的东西进行链接才能最终形成可执行程序。这就引出了我们今天要讨论的核心主角——库。3.2 为什么要有库——程序员之间的“浪漫协作”在平时写代码中比如说你写了个 Hello World代码里就几行是感觉不到库的存在的其实当你写下printf(Version1: 免费\n);或者std::cout hello yunze时你并没有自己实现打印功能的底层逻辑。库的本质就是由顶尖程序员实现并共享的基础功能集合。我们可以把库理解为一种“程序员之间的浪漫协作方式”或者说是技术界的“巨人的肩膀”。如果没有库每个程序员都要重复造轮子去写底层的打印、文件操作、数学计算等功能那将是巨大的人力浪费。有了库全球开发者可以直接调用这些现成的、高质量的代码极大地提高了开发效率。3.3 库在哪里——动态库与静态库的区别既然库这么重要那它们藏在哪里呢Windows下软件安装时所附带的后缀为lld的文件就是库在 Linux 系统中我们可以通过命令来一探究竟。3.3.1 查看系统库文件我们可以使用ls /lib64/libc.* -l命令来查看 C 语言标准库的文件[whbbite-alicloud lesson10]$ls/lib64/libc.*-l-rw-r--r--1root root5105516Jun42024/lib64/libc.a -rw-r--r--1root root253Jun42024/lib64/libc.so lrwxrwxrwx1root root12Oct162024/lib64/libc.so.6 -libc-2.17.so这里我们可以看到两种不同后缀的文件它们代表了库的两种存在形式操作系统动态库 (Dynamic Library)静态库 (Static Library)Windows.dll.libLinux.so(Shared Object).a(Archive)动态库 (.so)就像是一个公共的“小蚂蚁电竞馆”程序运行时去那里找功能。静态库 (.a)就像是把功能直接打包塞进了你的程序里。3.3.2 感性理解动态链接 vs 静态链接为了让大家更直观地理解这两者的区别我们用生活中的例子打个比方3.3.2.1 动态链接共享经济模式想象一下小王我们的程序要去云泽一中内存上学。他的课程表里有“上网”这一项。做法小王不需要自己买电脑他只需要知道“东门左100米有个网吧”这个地址。当他需要上网时他就跑去网吧动态库使用那里的电脑。优点节省资源如果有一百个学生都要上网只需要这一个网吧就够了大家共享。缺点一旦网吧倒闭库文件缺失所有依赖它的学生都无法上网了。在技术上动态链接通常是在程序运行时由加载器查找并运行库程序。3.3.2.2 静态链接私有财产模式还是小王要去上学这次他决定不依赖外面的网吧。做法小王的爸爸老板/编译器直接给他买了一台电脑装进书包里带去学校。无论走到哪他都用自己的电脑。优点程序不再依赖任何其他的库独立性强。缺点让可执行程序体积变大如果一百个学生每人都背一台电脑那就会非常沉这在计算机里意味着占据更多的内存空间造成资源浪费。3.4 实战演示默认动态链接与强制静态链接光说不练假把式我们通过实际的代码和命令来看看这两种链接方式的区别。3.4.1 准备测试代码我们编写一个简单的soft.c文件通过条件编译来模拟版本切换#includestdio.hvoidVersion1(){printf(Version1: 免费\n);}voidVersion2(){printf(Version2: 收费\n);}// #define FREE 1intmain(){#ifdefFREEVersion1();#elseVersion1();Version2();#endifreturn0;}3.4.2 默认情况动态链接在 Linux 下直接使用gcc编译默认采用的是动态链接。[whbbite-alicloud lesson10]$ gcc soft.c-osoft[whbbite-alicloud lesson10]$ ll -rwxrwxr-x1whb whb8424Nov1920:58 soft可以看到生成的soft文件只有8424 字节。我们可以用file命令确认它是动态链接的[whbbite-alicloud lesson10]$filesoft soft: ELF64-bit LSB executable, x86-64, version1(SYSV), dynamically linked(uses shared libs)...使用ldd命令可以查看它依赖了哪些动态库[whbbite-alicloud lesson10]$ ldd soft linux-vdso.so.1(0x00007fffefb923000)libc.so.6/lib64/libc.so.6(0x00007f8205584000)/lib64/ld-linux-x86-64.so.2(0x00007f8205952000)3.4.3 强制静态链接如果我们想让它变成静态链接需要在编译时加上-static参数[whbbite-alicloud lesson10]$ gcc soft.c-osoft-s-static[whbbite-alicloud lesson10]$ ll... -rwxrwxr-x1whb whb861352Nov1921:01 soft-s注意看文件大小从原来的 8KB 变成了861KB体积膨胀了上百倍。这就是因为我们将库的代码全部拷贝了一份塞进了程序里。再次验证类型[whbbite-alicloud lesson10]$filesoft-s soft-s: ELF64-bit LSB executable, x86-64, version1(GNU/Linux), statically linked...3.4.4 C 的情况对于 C 程序例如soft.cpp情况也是类似的。C 的标准库STL 等对应的动态库通常是libstdc.so。#includeiostreamintmain(){std::couthello yunzestd::endl;return0;}编译后使用ldd查看你会发现除了libc.so.6还多了一个libstdc.so.6的依赖。3.5 避坑指南静态链接报错怎么办在实际操作中你可能会遇到一个坑。当你尝试在 Linux 系统下进行强制静态链接-static时可能会报出如下错误/usr/bin/ld: cannotfind-lccollect2: error: ld returned1exitstatus原因分析这是因为 Linux 系统默认只安装了动态库为了节省空间而没有安装静态库。当你要求编译器去链接静态库-lc指的是 libc 的静态版时系统找不到对应的.a文件自然就报错了。解决方案你需要手动安装对应的静态库包。以 CentOS/Yum 为例执行以下命令即可yuminstallglibc-static libstdc-static-y安装完成后再次执行gcc soft.c -o soft-s -static就能顺利通过了。结语