云计算百科
云计算领域专业知识百科平台

编译器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.6(C标准库)的文件中。当我们编译程序时,GCC 会到系统默认的搜索路径(如 /usr/lib)下查找并链接这些库。

库的本质:就是一组预先编译好的、可复用的二进制代码(目标文件 .o 的集合)。根据链接方式的不同,分为静态库和动态库。

平台静态库后缀动态库后缀
Linux .a (Archive) .so (Shared Object)
Windows .lib .dll

2.2 静态链接

静态链接是指在编译链接时,把库文件的代码全部加入到可执行文件中。就像图片讲义中总结的:“把库中的方法(我们程序需要的),直接拷贝到你的程序中”。

生活比喻: 你想看一本书(使用库函数),静态链接的做法是直接去买一台复印机(将库代码拷贝进程序),把这本书复印一份放在家里。

  • 优点:以后看书再也不用出门了。程序运行时不再依赖外部库,运行速度快,移植方便。

  • 缺点:

  • 浪费空间:如果多个程序都用了 printf,那么每个程序里都有一份 printf 的副本,内存中存在大量重复代码。

  • 更新困难:如果库函数发现了 Bug 需要修改,所有使用了该静态库的程序都必须重新编译链接。

示例:

2.2.1. 首先,确保系统安装了静态库(一般云服务器默认没装):

# CentOS 系统安装静态库
yum install glibc-static libstdc++-static -y

2.2.2. 使用 -static 选项强制进行静态链接:

[x@localhost ~]$ gcc code.c -o code-static -static

2.2.3. 观察文件大小变化(注意 code 是动态链接,code-static 是静态链接):

[x@localhost ~]$ ll
-rwxr-xr-x 1 xin xin 8360 code
-rwxr-xr-x 1 xin xin 861288 code-static # 体积剧增!

2.2.4.验证链接方式:

[x@localhost ~]$ 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 hello

    2.3.2. 观察文件大小

    使用 ll 命令查看生成的可执行文件:

    [xin@my-centos ~]$ ll
    -rwxr-xr-x 1 xin xin 8360 Sep 29 15:00 hello

    你会发现 hello 只有区区 8360 字节(8KB左右)。如果是静态链接,这个文件通常会有 800KB 以上。这就是动态链接“节省空间”的直接体现。

    [xin@my-centos ~]$ file hello
    hello: ELF 64-bit LSB executable, x86-64, … dynamically linked …

    2.3.3. 查看依赖的动态库

    我们使用 ldd 命令来查看这个可执行程序到底依赖了哪些动态库:

    [xin@my-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 的快捷方式),指向真正的实体文件:

    [xin@my-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++)等,都是动态库。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 编译器gcc
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!