news 2026/8/1 16:41:33

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 翻译的四个阶段详解

简单回顾一下,不然感觉直接讲生态有些奇怪

  1. 预处理:这一步主要是把“不干净”的 C 语言变成“干净”的 C 语言。比如处理宏定义、头文件包含等,让代码变得纯粹。
  2. 编译:把我们对应的 C 语言翻译成汇编语言。这是从高级语言向低级语言跨越的关键一步。
  3. 汇编:把汇编语言编译成我们对应的二进制文件。这个文件叫做可重定位目标二进制文件,也简称为目标文件(后缀通常是.o)。
  4. 链接:最后将目标文件与库文件链接,生成最终的可执行程序。

所以,整个流程就是: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 编译器的诞生过程:

  1. 第一步(无中生有):最开始没有 C 语言编译器。科学家们只能用汇编语言,甚至直接用二进制,硬生生写出了第一代 C 语言编译器
    • 最早是用二进制写了第一版的汇编编译器,有了汇编编译器后,再用汇编写了第一版的 C 编译器。
  2. 第二步(自我复制):有了第一代编译器(它是用汇编写的,但能看懂 C 语言),我们就可以用 C 语言去写一个功能更强大的第二代编译器
  3. 第三步(编译升级):用第一代编译器,把第二代编译器的 C 语言源代码编译成可执行程序。
  4. 第四步(迭代发展):从此以后,C 语言和 C 语言写的编译器就形成了“双螺旋式”的发展,就像左脚踩右脚。C 语言出了新特性,就用旧版本的编译器去编译支持新特性的新版本编译器。

这个过程,我们就叫做编译器的自举

3.2 GCC 是用什么语言写的?

基于上面的原理,我们可以得出一个结论:GCC 本身就是用 C 语言写的。

如果你不信,我们可以现场验证一下。在 Linux 终端中,我们可以通过以下命令来看看 GCC 到底依赖什么库,从而推断它的“血统”。

[yunze@iZuf6bvbodyqq8qxjtf7l6Z lesson10]$whichgcc /usr/bin/gcc[yunze@iZuf6bvbodyqq8qxjtf7l6Z 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的动态链接库,我们可以看到它主要依赖libc(C 标准库)和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 语言标准库的文件:

[whb@bite-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.lib
Linux.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文件,通过条件编译来模拟版本切换:

#include<stdio.h>voidVersion1(){printf("Version1: 免费\n");}voidVersion2(){printf("Version2: 收费\n");}// #define FREE 1intmain(){#ifdefFREEVersion1();#elseVersion1();Version2();#endifreturn0;}

3.4.2 默认情况:动态链接

在 Linux 下,直接使用gcc编译,默认采用的是动态链接。

[whb@bite-alicloud lesson10]$ gcc soft.c-osoft[whb@bite-alicloud lesson10]$ ll -rwxrwxr-x1whb whb8424Nov1920:58 soft

可以看到生成的soft文件只有8424 字节。我们可以用file命令确认它是动态链接的:

[whb@bite-alicloud lesson10]$filesoft soft: ELF64-bit LSB executable, x86-64, version1(SYSV), dynamically linked(uses shared libs)...

使用ldd命令可以查看它依赖了哪些动态库:

[whb@bite-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参数:

[whb@bite-alicloud lesson10]$ gcc soft.c-osoft-s-static[whb@bite-alicloud lesson10]$ ll... -rwxrwxr-x1whb whb861352Nov1921:01 soft-s

注意看文件大小!从原来的 8KB 变成了861KB,体积膨胀了上百倍。这就是因为我们将库的代码全部拷贝了一份塞进了程序里。
再次验证类型:

[whb@bite-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

#include<iostream>intmain(){std::cout<<"hello yunze"<<std::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就能顺利通过了。


结语

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 16:38:13

ROS2 MPC

MoveIt2 ROS2 MPC 开源代码包汇总 分为两大类&#xff1a;线性 MPC (OSQP&#xff0c;快速原型)、非线性 NMPC (Acados&#xff0c;动力学&#xff0c;工业首选) 所有仓库均可直接git clone下载&#xff1b;环境默认 ROS2 Humble / Ubuntu22.04 一、ros-controls 官方&#xf…

作者头像 李华
网站建设 2026/8/1 16:32:32

3分钟彻底掌控macOS菜单栏:Ice让你的桌面整洁如新

3分钟彻底掌控macOS菜单栏&#xff1a;Ice让你的桌面整洁如新 【免费下载链接】Ice Powerful menu bar manager for macOS 项目地址: https://gitcode.com/GitHub_Trending/ice/Ice 还在为macOS菜单栏上密密麻麻的图标而烦恼吗&#xff1f;菜单栏空间被各种应用图标挤占…

作者头像 李华
网站建设 2026/8/1 16:26:55

如何在3分钟内打造你的专属智能桌面伴侣:BongoCat完全指南

如何在3分钟内打造你的专属智能桌面伴侣&#xff1a;BongoCat完全指南 【免费下载链接】BongoCat &#x1f431; 跨平台互动桌宠 BongoCat&#xff0c;为桌面增添乐趣&#xff01; 项目地址: https://gitcode.com/gh_mirrors/bong/BongoCat 想要为你的桌面增添一只聪明可…

作者头像 李华
网站建设 2026/8/1 16:26:53

2026,语音呼叫机器人从“可选”变“必选”——企业数字化转型的新基础设施

本文聚焦企业热线服务面临的三大痛点&#xff1a;高峰期电话打不进、724小时值守成本高、客户口语表达难以被准确理解。文章剖析语音呼叫机器人如何借助大模型原生能力&#xff0c;从“辅助工具”升级为企业数字化“新基础设施”。一、为什么语音呼叫机器人从“可选”变成“必选…

作者头像 李华
网站建设 2026/8/1 16:26:11

芯片供电设计:低电压大电流方案的原理、挑战与工程实践

1. 项目概述&#xff1a;从一次电源设计“翻车”说起 几年前&#xff0c;我参与一个嵌入式项目&#xff0c;核心是一颗高性能的FPGA芯片。为了追求极致的处理速度&#xff0c;我们团队最初设计了一套“高电压、小电流”的供电方案&#xff0c;想着电压高一点&#xff0c;动态响…

作者头像 李华