坚持用 清晰易懂的图解 + 代码语言,让每个知识点变得简单!
📌 呆头详细专栏系列
座右铭: "不患无位,患所以立。"
《Linux编译器:GCC/G++食用指南》
- 前言
- 目录
- 一、gcc/g++的简单使用
- [GCC/G++ 编译选项速查表](#GCC/G++ 编译选项速查表)
- 二、gcc/g++编译器的执行步骤
- 三、库
前言
🚀 欢迎来到《Linux系统实战》!
这里是命令行到内核的跃迁基地,也是你从"rm -rf恐惧症"到"权限管理大师"的修炼场。
🔍 专栏特色:
- 图解+实战:用最直观的方式拆解Linux核心机制
- 从应用到底层:覆盖Shell脚本、系统调优、内核模块开发
- 真实场景:每篇附服务器运维/开发中的实际问题解决方案
💡 学习建议 :
1️⃣ 先动手尝试(搞崩了也没关系)
2️⃣ 对照文章分析原理
3️⃣ 用文末【实战任务】巩固技能
📌 Linux经典名言 :
"Linux不是背出来的,是在一次次Permission denied
中练出来的!"
(正文开始👇)
目录
一、gcc/g++的简单使用
1.作用
gcc和g++分别是GNU的C和C++的编译器,gcc和g++在执行编译的时候一般有以下四个步骤:
1)预处理(头文件展开、去注释、宏替换、条件编译)。
2)编译(C代码翻译成汇编语言)。
3)汇编(汇编代码转为二进制目标代码)。
4)链接(将汇编过程产生的二进制代码进行链接)。
2.语法
GCC/G++ 编译选项速查表
选项 | 功能说明 | 备注 |
---|---|---|
-E |
只进行预处理,不生成文件 | 需手动重定向到输出文件(如 gcc -E test.c > test.i ) |
-S |
编译到汇编语言(生成 .s 文件),不进行汇编和链接 |
保留预处理 + 编译结果 |
-c |
编译到目标代码(生成 .o 文件),不链接 |
适用于分步编译 |
-o <file> |
指定输出文件名 | 如 gcc test.c -o test |
-static |
强制静态链接(生成文件较大) | 优先链接静态库而非动态库 |
-g |
生成调试信息(GDB 可用) | 默认生成 release 版本需显式添加此选项 |
-shared |
生成动态链接库(.so 文件) |
通常配合 -fPIC 使用 |
-w |
禁用所有警告信息 | 不推荐使用(可能掩盖潜在问题) |
-Wall |
开启所有标准警告信息 | 实际不包括所有警告(建议结合 -Wextra ) |
-O0 |
不优化(调试时推荐) | 保留原始代码结构 |
-O1 |
基础优化(默认级别) | 在编译速度和性能间平衡 |
-O2 |
深度优化(推荐发布使用) | 包含大多数安全优化选项 |
-O3 |
激进优化(可能增加代码体积) | 可能改变程序行为(需严格测试) |
小技巧:
- 组合使用示例:
g++ -Wall -O2 -g main.cpp -o app
- 查看完整选项:
gcc --help
或man gcc
二、gcc/g++编译器的执行步骤
gcc和g++的执行步骤中的指令都是相同的,这里小编以linux中的gcc编译器的执行步骤中的指令为例进行讲解
1.预处理
预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
- 预处理指令是以#号开头的代码行。
- 实例: gcc --E hello.c --o hello.i
- 选项"-E",该选项的作用是让 gcc 在预处理结束后停止编译过程。
- 选项"-o"是指目标文件,".i"文件为已经过预处理的C原始程序。
- 去掉注释
注释是写给开发者人员的自然对于程序的编译自然无用,所以编译器将对于编译阶段无用的注释去掉- 头文件的展开
头文件既然可以进行展开,那么说明在系统路径下一定有地方存放头文件,在预处理阶段将头文件的内容拷贝到源文件中- 条件编译
可以支持对代码的裁剪工作,例如应用的社区版和免费版其实就是应用了条件编译,使同一份代码经过条件编译后呈现出两种不同的状态- 宏替换
特别讲解一下这里的宏替换不进行语法检查,我们知道在编译阶段进行语法的检查,而宏替换是在编译阶段之前,即预处理阶段已经完成了宏替换,即预处理完之后由于宏已经被替换了,所以此时宏就没有了,自然在进行编译阶段不进行宏替换的语法检查。

2.编译
c
gcc -S code.i -o code.s
gcc -S 要进行编译的文件名(建议使用预处理后以 .i 为后缀的文件) -o 编译后的文件名(这里建议使用 .s 为后缀),即告诉编译器从现在开始进行翻译,当编译工作完成后,就停下来
- 在这个阶段中,gcc/g++首先检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,将代码翻译成汇编语言。
- 用户可以使用-S选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
- -o选项是指目标文件,"xxx.s"文件为已经过翻译的原始程序。
3.汇编
c
gcc -c code.s -o code.0
gcc -c 要进行汇编的文件名(这里建议使用编译后的.s文件) -o 汇编后的文件名(这里建议使用.o为后缀的文件名),告诉编译器开始进行程序的翻译,当汇编工作完成后,就停下来
- 汇编阶段是把编译阶段生成的"xxx.s"文件转成目标文件。
- 使用-c选项就可以得到汇编代码转化为"xxx.o"的二进制目标代码了。
4.链接
c
gcc code.o -o code
gcc 要进行链接的文件(这里建议使用执行完汇编后生成的.o文件) -o 链接后的文件名,将编译生成的可重定位目标二进制文件(目标文件)和库进行链接生成可执行程序
- 在成功完成以上步骤之后,就进入了链接阶段。
- 链接的主要任务就是将生成的各个"xxx.o"文件进行链接,生成可执行文件。
- gcc/g++不带-E、-S、-c选项时,就默认生成预处理、编译、汇编、链接全过程后的文件。
- 若不用-o选项指定生成文件的文件名,则默认生成的可执行文件名为a.out。
注意: 链接后生成的也是二进制文件。
* 程序执行 *
./可执行文件名,即可执行可执行文件
三、库
1.库的概念
- 在链接的时候会进行汇编生成的二进制文件和库进行链接,那么这个库具体是指的是什么呢?
- 拿c语言程序中的printf函数来说,在程序中调用printf打印hello,#include <stdio.h>头文件中是printf函数的声明,那么实现究竟是在哪里呢?没错,在库中,
- 在库中提供函数方法的实现,在c语言中即为c语言标准库
库的本质是文件,有其对应的路径在linxu中为(动态库)/usr/lib64/libc.so或(静态库)/usr/lib64/libc.a
2.库的分类
- linux:.so(动态库) .a(静态库)
- windows:.dll(动态库) .lib(静态库)
- 系统默认只提供动态库,为/usr/lib64/libc.so,但是对于静态库/usr/lib64/libc.a系统默认不提供即没有进行安装,这时候我们就要自行去安装静态库或动态库
动态库
c
# 安装运行时库(仅动态库,无头文件)
sudo yum install curl
# 安装开发包(包含头文件和动态库链接)
sudo yum install libcurl-devel
静态库
c
yum install glibc-static libstdc++-static -y

方法的实现其实就在库中
库其实是将源文件经过翻译,打包成为一个文件(不用提供太多的源文件,也达到了隐藏源文件的目的),库避免了一些经常性使用的函数实现的重复书写,节省工作,提高效率
你的软件=头文件提供声明+库提供函数的实现+你的代码
3 .o与库的链接
库分为动态库(共享库)和静态库,那么对应有动态链接和静态链接
动态链接:
- 动态链接是在程序执行时由运行时的链接文件加载库,所以很多程序的运行都要依赖动态库的存在,所以动态库不能缺失,一旦缺失会造成很多程序无法运行,进而可能造成无法使用操作系统
- 静态链接:
编译器在使用静态库进行静态链接的时候,会将静态库的方法拷贝到目标程序中,需要使用哪些函数方法就将哪些函数方法拷贝到目标文件中,并不是将静态库的所有方法都拷贝到目标文件中,这一点经常被混淆,请读者友友们注意区分,此后程序的执行不再需要依赖静态库在linux中,编译形成可执行程序,默认采用的链接方式是动态链接---动态库
ldd 可执行程序 可以查看或打印一个程序运行所需的共享库
观察一下静态链接方式由于是将库进行拷贝,所以占用空间明显大于动态链接(程序执行时由运行的链接文件进行加载库)方式
- 如果没有静态库,我们不能使用-static进行静态链接
- 如果没有动态库,只有静态库,并且编译器gcc可以找到静态库,此时编译器采用静态链接将我们的目标文件和库进行链接,因为当系统中存在动态库的时候,系统会优先采用动态链接,当不存在动态库的时候,编译器会再去看看系统中有没有静态库,如果有静态库则采用静态链接的方式,如果连静态库都没有则报错
- 同时在我们的可执行文件中,并不是所有的链接方式都是纯动态链接的,而是动态链接和静态链接混合的方式
- file 可执行程序,可以查看我们的可执行程序的链接方式
4.优缺点
以下是符合 CSDN 博客风格的 Markdown 表格,对比动态库和静态库的优缺点,并优化了可读性和技术专业性:
动态库 vs 静态库终极对比表
特性 | 动态库(.so/.dll) | 静态库(.a/.lib) |
---|---|---|
🔧 编译方式 | 编译时记录依赖,运行时加载 | 编译时直接嵌入到可执行文件中 |
📦 文件体积 | ✅ 极小(仅记录符号) • 多个程序共享同一份库文件 | ❌ 巨大(库代码直接复制到程序) • 每个程序都包含完整库代码 |
🏃 运行依赖 | ❌ 必须存在 • 缺失时报错:error while loading shared libraries |
✅ 完全独立 • 单文件即可运行 |
🔄 更新维护 | ✅ 热更新 • 替换 .so 文件立即生效 |
❌ 需重新编译 • 修复库=重新发布所有程序 |
⚡ 运行性能 | ⚠️ 略慢(首次加载需解析符号) | ✅ 极致快(无运行时链接开销) |
💣 兼容性 | ❌ 地狱级难题 • glibc 版本冲突常见 |
✅ 无烦恼 • 库和程序成绑定关系 |
🛡️ 安全性 | ⚠️ 有风险 • 可能被恶意替换成钓鱼库 | ✅ 铁布衫 • 代码全内嵌防篡改 |
🚀 适用场景 | • 桌面应用(如WPS) • 微服务容器 • 插件系统(如Nginx模块) | • 嵌入式设备 • 路由器固件 • 安全敏感工具(如tcpdump ) |
💻 部署难度 | ❌ 需处理依赖树 • LD_LIBRARY_PATH /rpath 配置复杂 |
✅ 双击即用 • 无环境依赖 |