当前位置: 首页 > news >正文

GCC/G++编译器详解:从编译原理到动静态链接

目录

一、背景知识

二、gcc编译选项

基本格式

1、预处理(进行宏替换)(.i/.ii)

2、编译(生成汇编)(.s)

3、汇编(生成机器可识别代码)(.o)

4、链接(生成可执行文件或库文件)

三、动态链接和静态链接

1、静态链接

2、动态链接

3、相关示例

(1)查看程序依赖库

(2)静态链接编译

​编辑错误原因

解决方法

1. 安装静态 C 库

 2. 验证安装

3. 完全静态链接(可选)

(3)验证文件类型

4、关于库的重要概念

四、静态库和动态库

五、gcc其他常用选项

补充说明

六、链接阶段详解:静态库 vs 动态库

1、核心概念

2、静态链接过程

3、动态链接过程

4、关键对比

七、问题谈讨

1、条件编译与软件版本分层的深度联系

2、为什么要进行程序翻译?

3、为什么需要学习汇编?

4、编译器自举

1. 自举三阶段

(1)初始阶段

(2)自举阶段

(3)成熟阶段

2. 自举的意义

3. 经典案例


一、背景知识

        GCC(GNU Compiler Collection)是一个功能强大的编译器套件,支持多种编程语言。在C/C++程序的编译过程中,通常分为以下四个阶段(按顺序执行):

  1. 预处理(Preprocessing)进行宏替换、去注释、条件编译、头文件展开等,生成纯净的代码.i/.ii

  2. 编译(Compilation):将预处理后的代码转换为汇编语言(.s

  3. 汇编(Assembly):将汇编代码生成机器可识别的目标代码(.o

  4. 链接(Linking):将目标文件库文件链接生成可执行文件(无后缀,如 ./program

阶段输入文件后缀输出文件后缀示例命令
预处理.c (C)、.cpp (C++).i (C)、.ii (C++)gcc -E source.c -o output.i
编译.i.c.cpp.s (汇编代码)gcc -S source.c -o output.s
汇编.s.o (目标文件)gcc -c source.s -o output.o
链接.o.a (静态库)、.so (动态库)无后缀 (默认 a.out) 或自定义gcc file1.o file2.o -o program

记忆小技巧:

        各选项可以形象记忆成键盘左上角的Esc键,只不过S是大写;各文件后缀可以形象记忆成iso,并不是iOS哦!!!


二、gcc编译选项

基本格式

gcc [选项] 要编译的文件 [选项] [目标文件]

1、预处理(进行宏替换).i/.ii

预处理阶段主要完成以下工作:宏定义展开、文件包含处理、条件编译、去除注释

预处理指令是以#号开头的代码行。

示例命令

gcc -E hello.c -o hello.i

选项说明

  • -E:让gcc在预处理结束后停止编译过程

  • -o:指定输出文件名,.i文件(内容很多)为已经过预处理的C原始程序

2、编译(生成汇编).s

编译阶段主要完成:

  1. 检查代码规范性和语法错误

  2. 将代码翻译成汇编语言

示例命令

gcc -S hello.i -o hello.s

选项说明-S:只进行编译而不进行汇编,生成汇编代码

3、汇编(生成机器可识别代码).o

汇编阶段将.s汇编文件转换为(可重定位目标二进制文件)目标文件(.o)。

示例命令

gcc -c hello.s -o hello.o

选项说明-c:进行汇编操作,生成二进制目标代码

4、链接(生成可执行文件或库文件)

  • 成功完成前期步骤后,程序进入链接阶段。该阶段的核心任务是将多个"xxx.o"目标文件链接整合,生成最终的可执行文件。
  • 使用gcc/g++时,若不指定-E、-S或-c选项,编译器将默认执行完整流程(包括预处理、编译、汇编和链接)。
  • 若未通过-o选项指定输出文件名,系统将默认生成名为a.out的可执行文件。
  • 链接阶段将目标文件与所需库文件合并,最终生成可执行二进制文件。

示例命令(选项还是-o)

gcc hello.o -o hello


三、动态链接和静态链接

        在实际开发中,程序通常由多个源文件组成,这些文件之间存在依赖关系。编译时每个.c文件会生成对应的.o目标文件,需要通过链接将这些目标文件组合成可执行程序。

1、静态链接

  • 特点:在编译链接时将库文件的代码全部加入到可执行文件中

  • 优点:执行时不需要依赖外部库,运行速度快

  • 缺点

    • 浪费空间(相同库代码在多个程序中重复存在)

    • 更新困难(库更新需要重新编译整个程序)

2、动态链接

  • 特点:程序运行时才加载所需的库

  • 优点

    • 节省磁盘和内存空间(多个程序共享同一个库)

    • 更新方便(只需更新库文件,无需重新编译程序),bin体积小,加载速度快。

  • 缺点运行时需要依赖正确的库版本,依赖动态库,程序可移植性较差。

3、相关示例

(1)查看程序依赖库

ldd hello

ldd命令用于打印程序或者库文件所依赖的共享库列表。 

输出示例

(2)静态链接编译

​gcc hello.o -o hello-s --static  # 强制静态链接

        我们想执行上面的命令,但是会报错: 在尝试静态链接时,编译器找不到 C 标准库的静态版本(libc.a

错误原因
  1. 缺少静态库

    • 系统未安装 glibc 的静态库(libc.a),导致无法完成静态链接。

    • 动态库(libc.so)通常默认安装,但静态库需要单独安装。

    • Linux一般只会存在动态库,所以gcc默认形成的可执行程序是动态链接的。

  2. 链接器提示:cannot find -lc 中的 -lc 表示链接器尝试查找 libc.a(C 标准静态库),但未找到。

解决方法
1. 安装静态 C 库

根据不同 Linux 发行版执行以下命令:

系统类型安装命令静态库路径(安装后)
Ubuntu/Debiansudo apt install libc6-dev/usr/lib/x86_64-linux-gnu/libc.a
CentOS/RHELsudo yum install glibc-static/usr/lib64/libc.a
Arch Linuxsudo pacman -S glibc/usr/lib/libc.a

 2. 验证安装
# 检查静态库是否存在
sudo find /usr -name "libc.a"  # 如果路径不在默认搜索目录,需手动指定库路径
gcc hello.o -o hello-s --static -L/usr/lib/x86_64-linux-gnu/

3. 完全静态链接(可选)

如果仍报错,可能需要安装其他依赖的静态库(如 libm.a):

sudo apt install libstdc++-static libgcc-static  # 安装 GCC 相关静态库

4.完成上面一系列操作后,我们再执行静态链接命令:

​gcc hello.o -o hello-s --static  # 强制静态链接

生成文件对比

  • 动态链接:hello(8KB)

  • 静态链接:hello-s(约861KB,含所有库代码)

(3)验证文件类型
file hello      # 显示动态链接
file hello-s    # 显示静态链接

输出示例

  • 动态链接:

  • 静态链接:

4、关于库的重要概念

  • C标准库函数(如printf)的实现位于libc.so.6库文件中。
  • gcc默认会在系统库路径(如/usr/lib)中查找并链接这些库。

四、静态库和动态库

特性静态库(.a)动态库(.so)
链接时机编译时运行时
文件大小较大较小
内存占用每个程序独立加载多个程序共享
更新维护需要重新编译只需替换库文件
运行速度稍快稍慢

注意:(XXX对应的是某种语言

类型Linux命名Windows命名链接时机文件特点
静态库lib.aXXX.lib编译时并入可执行文件体积大,独立运行
动态库libXXX.soXXX.dll运行时加载体积小,多程序共享

动态库是共享的,所以不能丢失,一旦丢失,所有依赖动态库的程序都会运行出错。

安装静态库(CentOS)

yum install glibc-static libstdc++-static -y

五、gcc其他常用选项

选项说明
-E只激活预处理,不生成文件
-S编译到汇编语言,不进行汇编和链接
-c编译到目标代码
-o指定输出文件名
-static使用静态链接
-g生成调试信息,供GDB使用
-shared尽量使用动态库
-O0/-O1/-O2/-O3优化级别,从O0(无优化)到O3(最高优化)
-w不生成任何警告信息
-Wall生成所有警告信息

补充说明

  • gcc默认使用动态链接,可通过file命令验证

  • 优化选项:-O1为默认值,-O3优化级别最高但可能增加编译时间


六、链接阶段详解:静态库 vs 动态库

1、核心概念

  1. 可重定位目标文件(.o

    • 由汇编器生成,包含未分配绝对地址的机器码(如 main.ofunc.o)。

    • 通过 objdump -d main.o 可查看未链接的符号(如 call printf 地址未确定)。

  2. 可执行程序:链接器合并所有 .o 和库文件后生成,包含绝对地址,可直接运行。

  3. 静态库(.a):一组 .o 的打包文件(如 libmath.a),编译时直接嵌入可执行程序。

  4. 动态库(.so/.dll):独立的外部库文件(如 libc.so),运行时由操作系统加载到内存共享。

2、静态链接过程

  1. 符号解析:链接器扫描所有 .o 和 .a,匹配未定义符号(如 printf)到库中的定义。

  2. 地址分配:为所有函数/变量分配固定内存地址(如 main 在 0x400000printf 在 0x500000)。(内存中执行)

  3. 代码合并:从静态库中仅提取用到的 .o,合并到最终可执行文件。

  4. 生成结果:文件体积大(含所有依赖库代码),独立运行。

示例命令

gcc main.o -o app --static -L. -lmath  # 链接静态库 libmath.a

3、动态链接过程

  1. 符号标记:链接器在可执行文件中记录依赖的动态库(如 NEEDED libc.so),但不嵌入库代码

  2. 延迟绑定:程序首次调用库函数时,由动态链接器(ld-linux.so)加载库到内存,并解析地址。

  3. 内存共享:多个程序可共享同一动态库的内存实例(如所有进程共用 libc.so 的代码段)。

示例命令

gcc main.o -o app -L. -lmath  # 默认动态链接 libmath.so

4、关键对比

特性静态链接动态链接
文件体积大(含库代码)小(仅记录依赖)
内存占用每个程序独占库副本多程序共享同一库
更新维护需重新编译替换 .so 文件即可生效
加载时机程序启动前完成运行时按需加载
典型场景嵌入式系统、独立分发大型软件、系统级库

七、问题谈讨

1、条件编译与软件版本分层的深度联系

        条件编译(Conditional Compilation)和软件版本分层(如社区版/专业版)是软件工程中紧密关联的两个核心概念,它们共同实现了同一套代码支持差异化功能的技术方案。

维度条件编译软件版本分层
实现手段预处理器指令(#ifdef/#define编译时宏定义组合
目标生成不同的二进制变体提供差异化产品功能
变更成本无需修改源代码,重新编译即可无需维护多套代码库
典型场景跨平台适配、调试模式社区版/专业版/企业版功能差异

商业软件案例

  • MySQL:社区版(GPL)与企业版(商业许可)使用相同代码库,通过条件编译禁用企业版的高可用插件。

  • Visual Studio:社区版禁用代码分析高级规则(通过#if !defined(COMMUNITY)实现)。

2、为什么要进行程序翻译?

程序翻译(编译)是将人类可读的高级语言转换为机器可执行代码的关键过程,其核心价值在于:

  • 跨平台兼容:通过编译适配不同硬件架构(x86/ARM)和操作系统(Linux/Windows)

  • 性能优化:编译器可对代码进行深度优化(如循环展开、内联函数)

  • 抽象封装:隐藏硬件细节,让开发者专注业务逻辑

  • 安全加固:编译时进行类型检查、内存越界检测等

3、为什么需要学习汇编?

汇编语言是连接高级语言与机器码的桥梁:

  • 逆向工程:分析恶意软件/漏洞利用的必备技能

  • 性能调优:直接优化关键代码段(如游戏引擎、数据库内核)

  • 嵌入式开发:资源受限场景(单片机、IoT设备)必须控制每条指令

  • 理解计算机体系结构:寄存器、内存管理、中断处理等核心概念

4、编译器自举

        编译器自举(Compiler Bootstrapping)是指用一门语言编写该语言自身的编译器(编译器也是软件),使编译器能“自己编译自己”的过程(证明一门语言能独立生存,不再依赖“外援”)。其核心是通过渐进式迭代,实现从初始简易版本到完整功能的闭环。具体分为三个阶段:

1. 自举三阶段

(1)初始阶段
  • 其他语言(如C)编写目标语言的初级编译器(功能有限,仅支持基础语法)。

  • 示例:第一个Go编译器用C写成,仅能编译Go的基本语法。

(2)自举阶段
  • 初级编译器编译一个更完善的编译器版本(此时新编译器用目标语言自身编写)。

  • 示例:Rust 1.0的编译器最初用OCaml编写,后来用Rust重写并通过旧编译器编译。

(3)成熟阶段
  • 后续版本完全用目标语言自身开发,新编译器既能编译自身,也能编译用户程序。

  • 示例:现代GCC的C++编译器已完全用C++编写。

2. 自举的意义

  • 验证语言完备性:能自举说明语言足够表达复杂逻辑。

  • 消除外部依赖:不再需要其他语言的编译器。

  • 提升编译器性能:用自身特性优化后续版本(如Rust的零成本抽象)。

3. 经典案例

语言初始编写语言自举版本当前状态
C汇编C完全自举(GCC)
GoCGo自举(Go 1.5+)
RustOCamlRust完全自举(Rustc)
http://www.dtcms.com/a/268414.html

相关文章:

  • 2025 JuniorCryptCTF re 部分wp
  • 【一起来学AI大模型】算法核心:数组/哈希表/树/排序/动态规划(LeetCode精练)
  • 【Docker基础】Docker数据卷管理:docker volume rm与prune命令对比
  • 计算机网络实验——配置ACL
  • vue3 当前页面方法暴露
  • 「Java题库」基础程序设计(理论+操作)
  • Excel 日期计算与最小日期选择(附示例下载)
  • DAY 49
  • monorepo + Turborepo --- 开发应用程序
  • Go语言实现双Token登录的思路与实现
  • 微服务基础:Spring Cloud Alibaba 组件有哪些?
  • 随机森林算法详解:Bagging思想的代表算法
  • 自存bro code java course 笔记(2025 及 2020)
  • 【Linux网络编程】Socket - UDP
  • CppCon 2018 学习:What do you mean “thread-safe“
  • Linux操作系统之文件(五):文件系统(下)
  • 数据库|达梦DM数据库安装步骤
  • 谷歌浏览器安全输入控件-allWebSafeInput控件
  • 黑布淡入淡出效果
  • Vue2 day07
  • STM32两种不同的链接配置方式
  • Python 中 ffmpeg-python 库的详细使用
  • CppCon 2018 学习:Undefined Behavior is Not an Error
  • Solidity——pure 不消耗gas的情况、call和sendTransaction区别
  • 【PyTorch】PyTorch中torch.nn模块的池化层
  • 汇编与接口技术:8259中断实验
  • Dify+Ollama+QwQ:3步本地部署,开启AI搜索新篇章
  • 1025 反转链表(附详细注释,逻辑分析)
  • 网络调式常用知识
  • 【机器学习笔记Ⅰ】1 机器学习