编译器gcc

发布时间:2026/10/1 10:24:54
编译器gcc 目录1. 从源码到可执行文件的四个阶段1.1. 预处理1.2. 编译1.3. 汇编1.4. 链接2. 静态库和动态库2.1 什么是库2.2 静态链接2.3 动态链接3. 总结1. 从源码到可执行文件的四个阶段在Linux环境下当我们使用gcc或g编译代码时看似简单的一条命令背后其实经历了四个复杂的阶段。下面结合实例演示创建一个名为hello.c的文件这四个阶段分别是1.1. 预处理预处理阶段是编译器真正开始工作前的准备。它主要处理以#开头的预处理指令。头文件展开将#include stdio.h替换为真实文件内容。宏替换将#define定义的宏如GREETING替换为实际值。去注释删除代码中的注释减少编译体积。条件编译根据#ifdef等指令决定保留哪些代码。-E让 gcc 在预处理结束后停止编译过程。-o指定输出文件.i文件为已经过预处理的 C 原始程序。1.2. 编译在这个阶段gcc 首先要检查代码的规范性、是否有语法错误。检查无误后gcc 把 C 代码翻译成汇编语言。这是整个流程中最耗时、最复杂的阶段包含了词法分析、语法分析、语义分析、中间代码生成和优化等步骤。-S只进行编译而不进行汇编生成汇编代码。生成的hello.s是文本文件里面包含了mov、push、call等汇编指令。1.3. 汇编汇编阶段是把编译阶段生成的.s文件转成目标文件Object File。汇编器将汇编代码翻译成机器能识别的二进制指令。生成的.o文件被称为可重定位目标二进制文件。-c将汇编代码转化为.o的二进制目标代码。1.4. 链接链接阶段是解决模块间相互依赖的问题。链接器将我们的.o文件与系统提供的标准库如libc.so链接起来解决函数调用的问题。我们在代码中使用的printf等函数其真正的实现在 C 标准库中。链接器会去查找printf在库中的地址并将其与我们的代码关联起来。执行后生成了可执行文件hello。此时这个文件才能被操作系统加载运行使用ldd hello命令可以查看可执行文件依赖的动态库可以看到hello成功链接到了libc.so.6这就是 C 语言标准库。可以找到该库所在的位置2.静态库和动态库2.1 什么是库在写 C 程序时我们经常使用printf函数。但仔细回想我们的代码里并没有定义printf包含的stdio.h里也只有它的声明没有实现。那么printf到底在哪里实现的呢答案是系统将这些常用函数的实现打包放进了名为libc.so.6C标准库的文件中。当我们编译程序时GCC 会到系统默认的搜索路径如/usr/lib下查找并链接这些库。库的本质就是一组预先编译好的、可复用的二进制代码目标文件.o的集合。根据链接方式的不同分为静态库和动态库。平台静态库后缀动态库后缀Linux.a(Archive).so(Shared Object)Windows.lib.dll2.2 静态链接静态链接是指在编译链接时把库文件的代码全部加入到可执行文件中。就像图片讲义中总结的“把库中的方法我们程序需要的直接拷贝到你的程序中”。生活比喻你想看一本书使用库函数静态链接的做法是直接去买一台复印机将库代码拷贝进程序把这本书复印一份放在家里。优点以后看书再也不用出门了。程序运行时不再依赖外部库运行速度快移植方便。缺点浪费空间如果多个程序都用了printf那么每个程序里都有一份printf的副本内存中存在大量重复代码。更新困难如果库函数发现了 Bug 需要修改所有使用了该静态库的程序都必须重新编译链接。示例2.2.1. 首先确保系统安装了静态库一般云服务器默认没装# CentOS 系统安装静态库 yum install glibc-static libstdc-static -y2.2.2. 使用-static选项强制进行静态链接[xlocalhost ~]$ gcc code.c -o code-static -static2.2.3. 观察文件大小变化注意code是动态链接code-static是静态链接[xlocalhost ~]$ ll -rwxr-xr-x 1 xin xin 8360 code -rwxr-xr-x 1 xin xin 861288 code-static # 体积剧增2.2.4.验证链接方式[xlocalhost ~]$ file code-static code-static: ELF 64-bit LSB executable, x86-64, ... statically linked ... # 提示 statically linked说明是静态链接2.3 动态链接动态链接的基本思想是把程序按照模块拆分成各个相对独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。简单来说可执行文件中不包含库的代码只包含库的地址引用。生活比喻你家你的程序只包含了你自己写的main函数代码。网吧动态库如libc.so.6提供了各种现成的服务如printf函数、数学计算等。去网吧上网函数跳转当你的程序需要打印一句话时它不会自己造一台打印机不像静态链接那样把代码拷贝进来而是拿着地址跑到网吧跳转到动态库借用网吧的打印机执行库函数打印完再回家返回程序。派出所系统环境负责管理这些网吧。如果网吧被查封了库文件丢失或者派出所把网吧的地址改了你再去上网就会找不到地方程序报错error while loading shared libraries。这个比喻完美诠释了动态链接的本质共享整个系统内存中libc.so只需要存在一份所有程序共用网吧只有一家大家都能去。依赖程序运行离不开这个外部环境网吧关门你就上不了网。基于上述原理动态链接拥有非常明显的优缺点优点节省空间可执行文件体积小且多个程序共享同一份库文件极大地节省了磁盘和内存空间。更新方便如果库函数发现了 Bug 需要修改只要接口不变所有依赖它的程序无需重新编译直接替换库文件即可网吧换了新电脑你依然可以去上网。缺点依赖性强一旦库缺失或版本不兼容所有相关程序都无法运行“一旦库缺失所有相关程序无法运行了”——引自笔记。性能略低需要额外的函数跳转寻址比静态链接稍微慢一点点但现代计算机完全可以忽略不计。示例2.3.1. 编译程序首先确保你有一个测试文件例如hello.c然后直接使用默认的gcc命令编译gcc hello.c -o hello2.3.2. 观察文件大小使用ll命令查看生成的可执行文件[xinmy-centos ~]$ ll -rwxr-xr-x 1 xin xin 8360 Sep 29 15:00 hello你会发现hello只有区区8360 字节8KB左右。如果是静态链接这个文件通常会有 800KB 以上。这就是动态链接“节省空间”的直接体现。[xinmy-centos ~]$ file hello hello: ELF 64-bit LSB executable, x86-64, ... dynamically linked ...2.3.3. 查看依赖的动态库我们使用ldd命令来查看这个可执行程序到底依赖了哪些动态库[xinmy-centos ~]$ ldd hello linux-vdso.so.1 (0x00007fff...) libc.so.6 /lib64/libc.so.6 (0x00007f...) /lib64/ld-linux-x86-64.so.2 (0x00007f...)ldd用于打印程序或者库文件所依赖的共享库列表。libc.so.6 /lib64/libc.so.6说明我们的程序依赖于 C 标准库并且系统找到了它在/lib64/目录下。2.3.4. 验证链接方式使用file命令确认可执行文件的链接属性dynamically linked明确提示这是动态链接。2.3.5. 底层原理查看库的真实面目如果你查看/lib64/libc.so.6你会发现它只是一个软链接类似于 Windows 的快捷方式指向真正的实体文件[xinmy-centos ~]$ ls -l /lib64/libc.so.6 lrwxrwxrwx 1 root root 12 Oct 16 2024 /lib64/libc.so.6 - libc-2.17.so这说明系统通过软链接的方式方便地进行库的版本管理比如以后升级了只需修改软链接指向libc-2.18.so即可程序不用动。3. 总结特性静态链接动态链接链接时机编译时拷贝代码进程序运行时加载库地址内存占用大每个程序都有副本小多个程序共享同一份库运行速度快无跳转开销略慢有函数跳转开销更新维护困难需重新编译所有程序容易只需替换库文件适用场景对性能要求极高、独立发布的工具大部分常规开发、系统级应用在实际开发中动态链接远比静态链接常用得多。这也是 GCC 默认的编译方式。它符合模块化开发的思想极大地节省了系统资源和维护成本。我们常用的 C 标准库、C 标准库libstdc等都是动态库。