晴天的雨.992头像

【Linux】编译器gcc/g++

本文面向 C/C++ 初学者,系统梳理 gcc/g++ 编译器的核心用法。全文围绕编译四大阶段(预处理、狭义编译、汇编、链接)展开,并深入对比静态库与动态库的区别,帮助读者理解从源文件到可执行文件的完整链路。此外,还整理了高频查看命令、头文件防重复包含写法、常用 gcc 选项速查表以及易混易错点,最后给出最简实操串。读者可按需定位到对应章节,快速掌握 gcc/g++ 的实用技能。

一、基础区分

  • gcc:面向 C 语言 的编译器
  • g++:面向 C++ 语言 的编译器

补充:gcc 也可以编译 C++,但不会自动链接 C++ 标准库;日常 C++ 开发优先直接用 g++。

二、C/C++ 程序编译四大阶段

下面用一张流程图直观展示从源文件到可执行文件的完整编译链路:

flowchart TD
    A[源文件 test.c / test.cpp] -->|gcc -E| B[预处理产物 test.i]
    B -->|gcc -S| C[汇编代码 test.s]
    C -->|gcc -c| D[目标文件 test.o]
    D -->|gcc test.o -o test| E[可执行文件 test]
A -. 阶段1:预处理 -E .-> A1[头文件展开 / 宏替换 / 删除注释]
B -. 阶段2:狭义编译 -S .-> B1[词法 / 语法 / 语义分析,生成汇编]
C -. 阶段3:汇编 -c .-> C1[汇编指令翻译成二进制机器码]
D -. 阶段4:链接 .-> D1[合并 .o / 解析符号 / 重定位 / 链接系统库]

源文件 .c / .cpp 分阶段执行,gcc 参数控制停在对应阶段

  1. 预处理(Preprocess) -E
    gcc -E test.c -o test.i
    
    • 工作:头文件 #include 展开、#define 宏替换、删除注释、#ifdef 条件编译筛选分支、处理 #pragma
    • 产物:.i 文件,纯文本,没有语法检查
  2. 狭义编译(Compile) -S
    gcc -S test.i -o test.s
    
    • 工作:词法分析、语法分析、语义检查,生成抽象语法树 AST,翻译成汇编指令
    • 产物:.s 汇编代码文件,报错大多集中在这个阶段
  3. 汇编(Assemble) -c
    gcc -c test.s -o test.o
    
    • 工作:汇编指令翻译成二进制机器码
    • 产物:可重定位目标文件 .o
    • 特点:包含机器码、符号表、重定位信息;符号地址未补齐,不能直接运行
  4. 链接(Link)
    gcc test.o -o test
    
    • 工作:合并多个 .o 目标文件并解析符号引用、重定位、链接系统库
    • 产物:ELF 格式可执行文件 / 库文件

一键编译:gcc test.c -o test,内部自动走完上面 4 步,不保留中间 .i / .s / .o

三、静态库与动态库

链接阶段的核心任务之一是把代码中用到的库函数合并进最终产物,而库分为静态库和动态库两种,二者在链接时机、产物形态和使用方式上差异明显。

3.1 静态库(Static Library)

  • 概念:在链接阶段把库中需要的目标文件代码直接复制合并进可执行文件,生成后不再依赖库文件。
  • 常见后缀:Linux 下为 .a(如 libm.a),Windows 下为 .lib。
  • 生成方式:先用 gcc -c 编译出目标文件,再用 ar 归档工具打包。
# 编译目标文件
gcc -c add.c -o add.o
打包成静态库
ar rcs libadd.a add.o
链接静态库
gcc main.c -L. -ladd -o main
  • 优点:程序运行时不依赖外部库文件,部署简单;加载速度快。
  • 缺点:可执行文件体积较大;库更新后需要重新编译链接整个程序。

3.2 动态库(Dynamic Library / Shared Library)

  • 概念:链接阶段只记录库的引用信息,不复制代码;程序运行时才由系统动态加载库文件到内存。
  • 常见后缀:Linux 下为 .so(如 libm.so),Windows 下为 .dll。
  • 生成方式:编译时加 -fPIC 生成位置无关代码,再用 -shared 生成动态库。
# 编译位置无关的目标文件
gcc -c -fPIC add.c -o add.o
生成动态库
gcc -shared add.o -o libadd.so
链接动态库
gcc main.c -L. -ladd -o main
  • 优点:可执行文件体积小;多个程序可共享同一份库,节省内存;库升级时无需重新编译主程序。
  • 缺点:运行时依赖库文件存在,缺失会报错;部署时需要一并携带库文件。

3.3 静态库与动态库对比

对比项静态库动态库
链接时机编译链接阶段程序运行时
Linux 后缀.a.so
Windows 后缀.lib.dll
可执行文件体积较大较小
运行时依赖不依赖库文件依赖库文件
库升级需重新编译链接替换库文件即可

补充:链接时若同时存在同名静态库和动态库,gcc 默认优先选择动态库;可用 -static 强制全部使用静态链接。

code>.dll动态库、.lib静态库

项目动态链接(默认)静态链接(-static)
命令gcc test.c -o testgcc test.c -o test_static -static
原理编译时只记录库符号;程序运行时才加载.so 共享库链接阶段,把用到的库代码直接拷贝进可执行文件
程序体积小大
依赖关系运行环境必须存在对应的.so库;库丢失程序无法启动打包后独立,不依赖外部库,移植方便
资源占用多个程序共享同一份库,节省内存磁盘每个程序自带一份库代码,存在代码冗余
查看命令ldd test 查看依赖的动态库file test_static 显示 statically linked;ldd 提示不是动态程序

⚠️ 注意:很多精简系统默认没有安装静态库,加-static会报cannot find -lc,Ubuntu 需要安装 libc6-dev-static

四、高频配套查看命令

  1. file 文件名:识别文件类型(源文件 / 目标文件 / 静态链接程序 / 动态链接程序)
  2. ldd 可执行文件:查看程序依赖哪些动态共享库
  3. which gcc:查看 gcc 编译器所在路径
  4. 系统文件位置
    • 系统头文件:/usr/include/(stdio.h 等)
    • 系统库文件:/lib64/

五、头文件防止重复包含(配套)

头文件被多次#include会重复定义,两种保护写法:

// 写法1:条件编译(兼容所有编译器)
#ifndef HEADER_NAME_H
#define HEADER_NAME_H
// 头文件内容
#endif
// 写法2:#pragma once(简洁,部分老编译器不支持)
#pragma once

六、常用 gcc 选项速查表

参数作用
-E只做预处理,输出.i
-S预处理 + 编译,输出.s 汇编
-c预处理 + 编译 + 汇编,输出.o 目标文件,不链接
-o xxx指定输出文件名
-static强制静态链接
-g加入调试信息,给 gdb 调试用
-O0 / -O1 / -O2 / -O3优化等级,O0 不优化,O3 优化最强
-Wall开启全部警告,推荐写代码带上

七、易混易错点

  1. .o目标文件不能直接执行:缺少符号地址的重定位,必须经过链接
  2. gcc 默认动态链接,不是静态链接
  3. 头文件只做声明,函数实现放在.c 源文件;链接阶段才找函数实现
  4. 预处理只是文本替换,不做语法检查,语法错误在 -S 编译阶段才报错

八、最简实操串

# 分步全套
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test
静态链接版本
gcc test.c -o test_static -static
一键编译+开启警告
gcc -Wall test.c -o test

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2603_95853495/article/details/168660894

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--