GCC/G++ 编译器完全指南:从编译流程到进阶用法(附实操案例) - 教程

在这里插入图片描述

草莓熊Lotso:个人主页

❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》

✨生活是默默的坚持,毅力是永久的享受!

博主简介:

在这里插入图片描述



前言:

在 Linux C/C++ 开发中,GCC(GNU Compiler Collection)是无可替代的编译器工具。无论是简单的 Hello World 程序,还是复杂的大型项目,GCC 都能完成从源代码到可执行文件的转换。但很多开发者只停留在gcc hello.c -o hello的基础用法,对其编译流程、链接原理和进阶选项了解甚少。本文结合核心知识点,从 GCC 的编译四阶段、核心选项、静态 / 动态链接,到优化与调试配置,全方位拆解 GCC 的使用技巧,帮你从 “会用” 升级到 “精通”。


一. GCC 核心认知:编译的四个阶段(ESc-iso速记)

GCC 编译 C/C++ 程序并非一步到位,而是分为预处理、编译、汇编、链接四个阶段,每个阶段完成特定任务,最终生成可执行文件。

1.1 阶段 1:预处理

  • 核心任务:宏替换、删除注释、条件编译、头文件展开(不进行语法检查);
  • 关键选项-E(仅执行预处理,停止后续编译);
  • 输出文件.i后缀(预处理后的 C 文件);
  • 实操命令
[Lotso@VM-4-4-centos lesson9]$ vim hello.c
[Lotso@VM-4-4-centos lesson9]$ gcc -E hello.c -o hello.i
[Lotso@VM-4-4-centos lesson9]$ ll
total 24
-rw-rw-r-- 1 Lotso Lotso   382 Nov 13 16:50 hello.c
-rw-rw-r-- 1 Lotso Lotso 17170 Nov 13 16:50 hello.i

在这里插入图片描述

  • 示例:#include <stdio.h>会被展开为 stdio.h 的全部内容,#define MAX 10会替换所有MAX为 10。

在这里插入图片描述

1.2 阶段 2:编译

  • 核心任务:检查语法错误,将预处理后的代码(.i)转换为汇编语言代码;
  • 关键选项-S(仅执行编译,停止后续流程);
  • 输出文件.s后缀(汇编文件);
  • 实操命令
[Lotso@VM-4-4-centos lesson9]$ gcc -S hello.c -o hello.s
[Lotso@VM-4-4-centos lesson9]$ ll
total 28
-rw-rw-r-- 1 Lotso Lotso   327 Nov 13 16:54 hello.c
-rw-rw-r-- 1 Lotso Lotso 17104 Nov 13 16:54 hello.i
-rw-rw-r-- 1 Lotso Lotso  1094 Nov 13 17:01 hello.s

在这里插入图片描述

  • 说明:生成的汇编代码与 CPU 架构相关(如 x86_64、ARM),可直接查看指令逻辑。
    在这里插入图片描述

1.3 阶段 3:汇编(Assembly)

  • 核心任务:将汇编代码(.s)转换为机器可识别的二进制目标文件;
  • 关键选项-c(仅执行汇编,生成目标文件);
  • 输出文件.o后缀(目标文件,二进制格式,不可执行);
  • 实操命令
[Lotso@VM-4-4-centos lesson9]$ gcc -c hello.s -o hello.o
[Lotso@VM-4-4-centos lesson9]$ ll
total 32
-rw-rw-r-- 1 Lotso Lotso   327 Nov 13 16:54 hello.c
-rw-rw-r-- 1 Lotso Lotso 17104 Nov 13 16:54 hello.i
-rw-rw-r-- 1 Lotso Lotso  2184 Nov 13 17:05 hello.o
-rw-rw-r-- 1 Lotso Lotso  1094 Nov 13 17:01 hello.s

在这里插入图片描述

[Lotso@VM-4-4-centos lesson9]$ objdump -d hello.o
hello.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
  0:	55                   	push   %rbp
  1:	48 89 e5             	mov    %rsp,%rbp
  4:	be 0a 00 00 00       	mov    $0xa,%esi
  9:	bf 00 00 00 00       	mov    $0x0,%edi
  e:	b8 00 00 00 00       	mov    $0x0,%eax
  13:	e8 00 00 00 00       	callq  18 <main+0x18>
    18:	be 0a 00 00 00       	mov    $0xa,%esi
    1d:	bf 00 00 00 00       	mov    $0x0,%edi
    22:	b8 00 00 00 00       	mov    $0x0,%eax
    27:	e8 00 00 00 00       	callq  2c <main+0x2c>
      2c:	be 0a 00 00 00       	mov    $0xa,%esi
      31:	bf 00 00 00 00       	mov    $0x0,%edi
      36:	b8 00 00 00 00       	mov    $0x0,%eax
      3b:	e8 00 00 00 00       	callq  40 <main+0x40>
        40:	be 0a 00 00 00       	mov    $0xa,%esi
        45:	bf 00 00 00 00       	mov    $0x0,%edi
        4a:	b8 00 00 00 00       	mov    $0x0,%eax
        4f:	e8 00 00 00 00       	callq  54 <main+0x54>
          54:	be 0a 00 00 00       	mov    $0xa,%esi
          59:	bf 00 00 00 00       	mov    $0x0,%edi
          5e:	b8 00 00 00 00       	mov    $0x0,%eax
          63:	e8 00 00 00 00       	callq  68 <main+0x68>
            68:	be 0a 00 00 00       	mov    $0xa,%esi
            6d:	bf 00 00 00 00       	mov    $0x0,%edi
            72:	b8 00 00 00 00       	mov    $0x0,%eax
            77:	e8 00 00 00 00       	callq  7c <main+0x7c>
              7c:	be 0a 00 00 00       	mov    $0xa,%esi
              81:	bf 00 00 00 00       	mov    $0x0,%edi
              86:	b8 00 00 00 00       	mov    $0x0,%eax
              8b:	e8 00 00 00 00       	callq  90 <main+0x90>
                90:	be 0a 00 00 00       	mov    $0xa,%esi
                95:	bf 00 00 00 00       	mov    $0x0,%edi
                9a:	b8 00 00 00 00       	mov    $0x0,%eax
                9f:	e8 00 00 00 00       	callq  a4 <main+0xa4>
                  a4:	be 0a 00 00 00       	mov    $0xa,%esi
                  a9:	bf 00 00 00 00       	mov    $0x0,%edi
                  ae:	b8 00 00 00 00       	mov    $0x0,%eax
                  b3:	e8 00 00 00 00       	callq  b8 <main+0xb8>
                    b8:	be 0a 00 00 00       	mov    $0xa,%esi
                    bd:	bf 00 00 00 00       	mov    $0x0,%edi
                    c2:	b8 00 00 00 00       	mov    $0x0,%eax
                    c7:	e8 00 00 00 00       	callq  cc <main+0xcc>
                      cc:	be 0a 00 00 00       	mov    $0xa,%esi
                      d1:	bf 00 00 00 00       	mov    $0x0,%edi
                      d6:	b8 00 00 00 00       	mov    $0x0,%eax
                      db:	e8 00 00 00 00       	callq  e0 <main+0xe0>
                        e0:	b8 00 00 00 00       	mov    $0x0,%eax
                        e5:	5d                   	pop    %rbp
                        e6:	c3                   	retq
  • 说明:目标文件包含机器指令,但缺少库依赖(如 printf 函数的实现),无法直接运行。
[Lotso@VM-4-4-centos lesson9]$ ./hello.o
-bash: ./hello.o: Permission denied

1.4 阶段 4:链接(Linking)

核心任务:将目标文件(.o)与系统库、第三方库链接,生成可执行文件;
核心逻辑:解析未定义符号(如 printf),从库文件(如 libc.so)中找到实现并关联;
输出文件:默认名为a.out(可通过-o指定名称);
实操命令

# 也可以 gcc -o hello.exe hello.o
[Lotso@VM-4-4-centos lesson9]$ gcc hello.o -o hello.exe
[Lotso@VM-4-4-centos lesson9]$ ll
total 44
-rw-rw-r-- 1 Lotso Lotso   327 Nov 13 16:54 hello.c
-rwxrwxr-x 1 Lotso Lotso  8360 Nov 13 17:11 hello.exe
-rw-rw-r-- 1 Lotso Lotso 17104 Nov 13 16:54 hello.i
-rw-rw-r-- 1 Lotso Lotso  2185 Nov 13 17:05 hello.o
-rw-rw-r-- 1 Lotso Lotso  1094 Nov 13 17:01 hello.s
[Lotso@VM-4-4-centos lesson9]$ ./hello.exe
10
10
10
10
10
10
10
10
10
10
10

在这里插入图片描述

1.5 一键编译(合并四阶段)

日常开发中无需分步执行,GCC 支持一键完成四阶段编译:

# 也可以 gcc -o hello.exe1 hello.c
[Lotso@VM-4-4-centos lesson9]$ gcc hello.c -o hello.exe1
[Lotso@VM-4-4-centos lesson9]$ ll
total 56
-rw-rw-r-- 1 Lotso Lotso   327 Nov 13 16:54 hello.c
-rwxrwxr-x 1 Lotso Lotso  8360 Nov 13 17:11 hello.exe
-rwxrwxr-x 1 Lotso Lotso  8360 Nov 13 17:15 hello.exe1
-rw-rw-r-- 1 Lotso Lotso 17104 Nov 13 16:54 hello.i
-rw-rw-r-- 1 Lotso Lotso  2185 Nov 13 17:05 hello.o
-rw-rw-r-- 1 Lotso Lotso  1094 Nov 13 17:01 hello.s
[Lotso@VM-4-4-centos lesson9]$ ./hello.exe1
10
10
10
10
10
10
10
10
10
10
10

在这里插入图片描述
本质是 GCC 自动依次执行 预处理→编译→汇编→链接,隐藏了中间文件。
在这里插入图片描述


二. GCC核心功能选项 && 条件编译 && 编译器和语言的历史(理解编译的过程)

2.1 核心功能速查表:

选项功能描述使用示例
-E只进行预处理,不编译、汇编和链接gcc -E main.c -o main.i
-S编译到汇编语言,不进行汇编和链接gcc -S main.c -o main.s
-c编译到目标代码,生成.o文件gcc -c main.c -o main.o
-o指定输出文件名gcc main.c -o myapp
-static使用静态链接生成可执行文件gcc main.c -static -o app_static
-g生成调试信息,供GDB使用gcc -g main.c -o debug_app
-shared尽量使用动态库,生成较小的文件gcc -shared lib.c -o lib.so
-O0不进行优化(编译速度最快)gcc -O0 main.c -o app
-O1基本优化(默认级别)gcc -O1 main.c -o app
-O2较多优化,平衡性能与编译时间gcc -O2 main.c -o app
-O3最高级别优化(可能增加代码大小)gcc -O3 main.c -o app
-w禁止所有警告信息gcc -w main.c -o app
-Wall开启所有常见警告信息gcc -Wall main.c -o app

2.2 条件编译的逻辑和应用场景

在这里插入图片描述

2.3 编译的过程:编译器和语言的历史

在这里插入图片描述
在这里插入图片描述
关键逻辑

  • 为什么任何语言最后都要变成二进制 -> 因为编译器只认识它
  • 编译的过程为何是那样的 -> 上面的图中有解释
  • 一定是先有语言再有编译器的。并且语言和编译器之间是一个自举的关系

三. 关键原理:静态链接和动态链接

链接阶段的核心是"关联库文件",GCC支持两种链接方式,各有优劣
在这里插入图片描述

3.1 静态链接(Static Linking)

  • 原理:编译时将依赖的库文件(如 libc.a)全部复制到可执行文件中;
  • 关键选项-static
  • 实操命令gcc hello.c -o hello_static -static – 强制静态链接生成可执行文件(因为GCC默认是动态链接的)
  • 安装命令sudo yum install glibc-static libstdc++-static -y
    在这里插入图片描述
  • 优点:可执行文件不依赖系统库,移植性强。
  • 缺点:会让可执行程序变大,运行的时候,加载到内存:占据更多的内存空间,浪费资源!!!

3.2 动态链接(Dynamic Linking)

  • 原理:编译时仅记录库依赖(如 libc.so),程序运行时才加载库文件;
  • 特点:GCC 默认采用动态链接;
  • 实操命令gcc hello.c -o hello_dynamic – 动态链接(默认)
  • 优点:文件体积小,库文件被多个程序共享,节省内存和磁盘空间;
  • 缺点:依赖系统中对应的库文件,一旦库文件缺失,所有程序都无法运行了。

在这里插入图片描述
在这里插入图片描述

3.2 查看依赖的库文件

通过 ldd 可以查看可执行程序依赖的动态库

# 动态链接的文件
[Lotso@VM-4-4-centos lesson10]$ ldd hello_dynamic
linux-vdso.so.1 =>  (0x00007ffe61b15000)
libc.so.6 => /lib64/libc.so.6 (0x00007f2e519cd000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2e51d9b000)
# 静态链接的文件
[Lotso@VM-4-4-centos lesson10]$ ldd hello_static
not a dynamic executable

在这里插入图片描述
顺便看看动态链接和静态链接文件的大小对比

在这里插入图片描述

3.3 通过一个故事感性的理解一下库

3.3.1 阶段一:动态库的故事(共享网吧模式)

小王是一中的新生,他学习刻苦,但也需要偶尔上网放松。一中管理严格,但小王从学长张三那里得知,学校东门左转100米再右转100米有一家“小蚂蚁电竞馆”。小王记下地址,在某个周六中午,他按计划完成部分作业后,前往电竞馆上网。老板给他开了编号1234的电脑,小王愉快地度过了中午。下午2:30,他回到学校继续学习。

后来,越来越多的学生知道了电竞馆,纷纷前去放松。直到月考,全班成绩下滑,校长调查后举报导致了电竞馆关闭。

✅️ 阶段一理解

  • 小王(程序)在一中(内存)学习。

  • 张三(编译器/链接器)告诉小王电竞馆的地址(动态库的位置)。

  • 小王去电竞馆上网(程序运行时链接动态库)。

  • 老板给小王开1234号电脑(动态库分配给程序一个函数地址)。

  • 电竞馆关闭(动态库缺失)导致所有学生无法上网(程序无法运行)。

声明:这里是为了方便故事举例说明哈,动态库其实也是在内存里的

图示理解
在这里插入图片描述

┌─────────────┐     询问地址     ┌─────────────┐
│    小王      │ ──────────────> │     张三     │
│   (程序)     │                 │ (编译器/链接器)│
└─────────────┘                 └─────────────┘
│                            │
│ 获得地址:"东门左转100m右转100m"
│                            │
▼                            ▼
┌─────────────────────────────────────────────┐
│                   一中                       │
│                   (内存)                     │
└─────────────────────────────────────────────┘
│
│ 按计划执行:作业→上网→作业
│
▼
┌─────────────┐     上网请求     ┌─────────────┐
│    小王      │ ──────────────> │ 小蚂蚁电竞馆   │
│   (程序)     │                 │   (动态库)    │
└─────────────┘                 └─────────────┘
│
│ 分配1234号电脑
│ (库函数地址)
▼
┌─────────┐
│ 愉快上网 │
│ (函数执行)│
└─────────┘

动态库特点

  • 优点:资源共享,节省内存(多个程序共用同一个库)

  • 缺点:库缺失则所有依赖程序都无法运行

3.3.2 阶段二:静态库的故事(自带电脑模式)

小王因不能上网而学习状态不佳,父亲了解情况后,与校长协商并去小蚂蚁电竞馆找老板买下那台1234号电脑,放在学校供小王使用。此后,小王和同学们都可以在学校内直接上网,无需外出。

✅️ 阶段二理解

  • 小王的父亲和电竞馆老板(静态库的提供者)将电脑(库函数)买下并安置在学校(静态链接)。

  • 小王和同学们(多个程序)每人都拥有了一份电脑的拷贝(静态链接时每个程序都包含一份库代码)。

  • 现在上网(使用库函数)不依赖校外电竞馆(外部动态库),但每个人都需要占用自己的电脑(内存空间变大)。

图示理解
在这里插入图片描述

┌─────────────┐   成绩下滑      ┌─────────────┐
│    小王      │ ──────────────> │    爸爸      │
│   (程序)     │                 │ (静态链接器)  │
└─────────────┘                 └─────────────┘
│                            │
│         协商购买            │
│                            │
▼                            ▼
┌─────────────┐   购买1234电脑   ┌─────────────┐
│    校长      │ <────────────── │ 电竞馆老板    │
│  (系统环境)  │                 │  (库提供者)   │
└─────────────┘                 └─────────────┘
│
│ 电脑安置在学校
│
▼
┌─────────────────────────────────────────────┐
│                   一中                       │
│                   (内存)                     │
└─────────────────────────────────────────────┘
│
│ 每个学生都自带电脑
│ (程序各自包含库代码)
▼
┌─────────────┐   直接使用      ┌─────────────┐
│  小王和室友   │ ──────────────> │  个人电脑    │
│   (程序)     │                 │   (静态库)   │
└─────────────┘                 └─────────────┘

静态库特点

  • ✅ 优点:程序独立,不依赖外部环境

  • ❌ 缺点:每个程序都包含库代码,占用更多存储空间和内存

3.3.3 核心概念对比 && 整体总结

特性动态库(小蚂蚁电竞馆)静态库(个人电脑)
资源使用共享,节省资源独占,浪费资源
依赖性强依赖,库缺失则全崩独立,自给自足
更新维护更新库即更新所有程序每个程序需重新编译
内存占用多个程序共享同一份每个程序都有一份拷贝

现实世界类比

编程世界:                   现实世界:
┌─────────────────┐        ┌─────────────────┐
│     动态库       │        │    共享网吧      │
│  (共享函数集合)   │        │  (共享电脑资源)   │
└─────────────────┘        └─────────────────┘
│                          │
│ 多个程序共用              │ 多个顾客共用
│ 库缺失=所有程序挂掉       │ 网吧关门=所有人都不能上网
│                          │
┌─────────────────┐        ┌─────────────────┐
│     静态库       │        │    个人电脑      │
│ (函数嵌入程序内)  │        │  (自有电脑资源)   │
└─────────────────┘        └─────────────────┘
│                          │
│ 每个程序都包含库代码      │ 每个人都有自己的电脑
│ 程序体积大但独立运行      │ 随时可用但占用更多空间

整体总结:
通过这两个故事,我们理解了动态库和静态库的区别:

  • 动态库:像是一个共享的电竞馆,多个程序在运行时共享同一个库,节省空间,但一旦库被删除,所有程序都无法运行。

  • 静态库:像是每个人都有自己的电脑,程序在编译时就将库代码复制到可执行文件中,因此程序体积大,但可以独立运行,不依赖外部库。

在编程中,我们根据需求选择使用动态库还是静态库。动态库适合多个程序共享,减少内存占用;静态库适合程序独立发布,避免依赖问题。



结尾:

 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:GCC 的核心价值在于 “灵活与强大”—— 通过不同选项组合,可适配调试、发布、移植等多种场景。掌握编译四阶段让你理解代码转换的本质,静态 / 动态链接的选择影响项目的移植性和性能,而优化与调试选项则能提升开发与运行效率。建议在实际项目中多尝试组合选项(如-Wall -O2 -g),逐步形成适合自己的编译习惯。如果需要编译 C++ 程序,只需将gcc替换为g++,核心用法完全一致。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

在这里插入图片描述

posted @ 2026-01-09 12:01  clnchanpin  阅读(283)  评论(0)    收藏  举报