GCC 编译器的使用

目录

[1. 编译的四个阶段(总览)](#1. 编译的四个阶段(总览))

[2. 一步步手动体验(推荐亲手试)](#2. 一步步手动体验(推荐亲手试))

[2.1 预处理(-E)](#2.1 预处理(-E))

[2.2 编译(-S)](#2.2 编译(-S))

[2.3 汇编(-c)](#2.3 汇编(-c))

[2.4 链接(-o)](#2.4 链接(-o))

[3. 常用编译选项详解](#3. 常用编译选项详解)

[4. 多文件编译的两种方式](#4. 多文件编译的两种方式)

方式一:一步到位

方式二:分开编译,统一链接(推荐大型项目)

[5. 动态库(共享库)的制作与使用](#5. 动态库(共享库)的制作与使用)

[5.1 制作动态库](#5.1 制作动态库)

[5.2 使用动态库](#5.2 使用动态库)

[5.3 运行程序](#5.3 运行程序)

[6. 静态库的制作与使用](#6. 静态库的制作与使用)

[6.1 制作静态库](#6.1 制作静态库)

[6.2 使用静态库](#6.2 使用静态库)

[7. 很有用的附加选项(调试与查错)](#7. 很有用的附加选项(调试与查错))

[8. 交叉编译说明](#8. 交叉编译说明)

[9. 总结](#9. 总结)


欢迎来到 GCC 编译器的世界!如果你刚开始接触 Linux 下的 C/C++ 开发,可能会对一堆编译选项感到困惑。别担心,本文将从零开始,把 GCC 的编译过程拆成最细的步骤,每一步都配上通俗的解释和实际命令,让你不仅理解,还能自己动手操作。以后遇到编译相关的问题,也可以随时回来查阅。


1. 编译的四个阶段(总览)

一个 C/C++ 源文件变成可执行文件,需要经过 预处理编译汇编链接 四个步骤。每个步骤都会生成中间文件,直到最后得到可以直接运行的程序。

下面的流程图可以帮你建立直观印象:

复制代码
text

.c / .cpp   (源代码)
    │
    │ 预处理 (Preprocessing)
    ▼
.i          (预处理后的文件)
    │
    │ 编译 (Compilation)
    ▼
.s          (汇编代码文件)
    │
    │ 汇编 (Assembly)
    ▼
.o          (目标文件,机器码但不可独立运行)
    │
    │ 链接 (Linking) + 其他目标文件/库
    ▼
可执行文件 (例如 hello, a.out)

每个阶段对应的 GCC 选项:

阶段 选项 输入 输出 示例命令
预处理 -E hello.c hello.i gcc -E -o hello.i hello.c
编译 -S hello.i hello.s gcc -S -o hello.s hello.i
汇编 -c hello.s hello.o gcc -c -o hello.o hello.s
链接 无选项(或-o hello.o hello gcc -o hello hello.o

注意:-c 选项实际上是"预处理 + 编译 + 汇编"三步的合并,但不链接。而直接用 gcc hello.c 会完成所有四个步骤,默认生成 a.out


2. 一步步手动体验(推荐亲手试)

让我们从一个最简单的 hello.c 文件开始,手动执行每一个步骤,看看中间文件都长什么样。

cs 复制代码
c

// hello.c
#include <stdio.h>
#define GREETING "Hello, world!"

int main() {
    printf("%s\n", GREETING);
    return 0;
}

2.1 预处理(-E

预处理会做三件事:头文件展开宏替换删除注释。执行:

bash 复制代码
bash

gcc -E -o hello.i hello.c

现在打开 hello.i,你会看到 #include <stdio.h> 被替换成了几百行代码,GREETING 被替换成了字符串。这个文件仍然是文本,但已经没有了宏和 #include。

2.2 编译(-S

编译将预处理后的 .i 文件翻译成汇编代码(人类可读的汇编语言)。执行:

bash 复制代码
bash

gcc -S -o hello.s hello.i

打开 hello.s,你会看到类似 movl, pushq 这样的指令,这是针对你当前 CPU 架构的汇编代码。

2.3 汇编(-c

汇编器将汇编代码转换成机器码 (二进制),生成目标文件.o.obj)。目标文件已经是二进制了,但还不能直接运行,因为它可能还依赖其他函数(比如 printf)。执行:

bash 复制代码
bash

gcc -c -o hello.o hello.s

现在 hello.o 是一个二进制文件,可以用 file hello.o 查看文件类型。

2.4 链接(-o

链接器将目标文件和所需的库(比如 C 标准库)合并,生成最终的可执行文件。执行:

bash 复制代码
bash

gcc -o hello hello.o

(也可以省略 -o hello,默认生成 a.out)。运行它:

bash 复制代码
bash

./hello

输出:Hello, world!


3. 常用编译选项详解

下面列出最实用的选项,每个都会配一个使用场景。

选项 含义 典型用法
-E 只进行预处理,不编译、汇编、链接 想查看宏展开结果:gcc -E -dM hello.c > macros.txt
-c 执行预处理、编译、汇编,但不链接 生成目标文件,用于后续单独链接:gcc -c -o main.o main.c
-o <file> 指定输出文件名 默认输出是 a.out,用 -o 改名:gcc -o myprog main.c
-I <dir> 添加头文件搜索目录 #include "myheader.h" 不在标准路径时:gcc -I../include -c main.c
-L <dir> 添加库文件搜索目录 链接时告诉链接器去哪个目录找库:gcc -o test main.o -L./libs -lmylib
-l<name> 链接名为 lib<name>.solib<name>.a 的库 链接数学库:-lm;链接自己建的库:-lmylib
-static 强制静态链接 生成的可执行文件不依赖动态库,体积大但移植性好:gcc -static -o hello hello.c
-v 显示详细的编译过程(调试用) 可以看到实际调用的子程序(cc1、as、collect2):gcc -v -o hello hello.c

4. 多文件编译的两种方式

假设你有两个源文件:main.csub.c,想生成 test 可执行文件。

方式一:一步到位

bash 复制代码
bash

gcc -o test main.c sub.c

GCC 会自动完成所有步骤,生成最终可执行文件 test

方式二:分开编译,统一链接(推荐大型项目)

bash 复制代码
bash

# 分别编译成目标文件
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c

# 链接目标文件
gcc -o test main.o sub.o

这种方式的好处是:如果只修改了 main.c,只需重新编译 main.o,再链接一次即可,节省时间。


5. 动态库(共享库)的制作与使用

动态库在程序运行时才被加载,多个程序可以共享一份库代码,节省内存。

5.1 制作动态库

假设有 sub.csub2.csub3.c,你想把它们打包成一个动态库 libsub.so

bash 复制代码
bash

# 先编译成目标文件(注意:-fPIC 是位置无关代码,动态库必需)
gcc -c -fPIC -o sub.o sub.c
gcc -c -fPIC -o sub2.o sub2.c
gcc -c -fPIC -o sub3.o sub3.c

# 用 -shared 生成动态库
gcc -shared -o libsub.so sub.o sub2.o sub3.o

5.2 使用动态库

假设你的主程序 main.c 调用了库中的函数,编译时:

bash 复制代码
bash

gcc -c -o main.o main.c
gcc -o test main.o -L. -lsub
  • -L. 告诉链接器在当前目录(.)查找库文件。

  • -lsub 告诉链接器链接 libsub.solib 前缀和 .so 后缀自动补全)。

5.3 运行程序

动态库在程序运行时必须能被找到。有三种常见方法:

  1. 将库复制到系统标准目录 (如 /lib/usr/lib):

    bash 复制代码
    bash
    
    sudo cp libsub.so /lib
    ./test
  2. 临时设置环境变量 LD_LIBRARY_PATH

    bash 复制代码
    bash
    
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
    ./test
  3. 将库路径写入可执行文件 (使用 -rpath 链接选项,这里不展开)。


6. 静态库的制作与使用

静态库在链接时会被直接整合到可执行文件中,运行时不再需要库文件。

6.1 制作静态库

静态库本质上是多个 .o 文件的归档,使用 ar 工具创建。

bash 复制代码
bash

# 先编译目标文件(静态库不需要 -fPIC)
gcc -c -o sub.o sub.c
gcc -c -o sub2.o sub2.c
gcc -c -o sub3.o sub3.c

# 用 ar 打包成静态库 libsub.a
ar crs libsub.a sub.o sub2.o sub3.o

6.2 使用静态库

bash 复制代码
bash

gcc -c -o main.o main.c
gcc -o test main.o libsub.a

或者用 -l-L 选项(链接器会自动在 -L 路径下找 libsub.a):

bash 复制代码
bash

gcc -o test main.o -L. -lsub
运行程序时不需要额外的库,直接 ./test 即可。

7. 很有用的附加选项(调试与查错)

以下选项在开发中经常用来检查问题或生成辅助文件:

选项 作用
gcc -E main.c 只预处理并输出到屏幕,可以快速查看宏展开后的代码。
gcc -E -dM main.c > 1.txt 列出文件中定义的所有宏(包括系统宏),输出到文件。
gcc -Wp,-MD,abc.dep -c -o main.o main.c 生成依赖文件 abc.dep(Makefile 中常用,记录头文件依赖)。
`echo 'main(){}' gcc -E -v -`
gcc -v -o test main.c 显示完整的编译过程,包括调用的子程序。

8. 交叉编译说明

如果你的开发环境是 x86 PC,但目标机器是 ARM(例如树莓派、嵌入式设备),你需要使用交叉编译工具链,例如 arm-linux-gnueabihf-gcc

用法和普通 GCC 完全一样,只是命令前缀不同:

bash 复制代码
bash

arm-linux-gnueabihf-gcc -o hello hello.c        # 编译 ARM 版本的可执行文件
arm-linux-gnueabihf-gcc -c -fPIC -o sub.o sub.c # 编译 ARM 的动态库目标文件

交叉编译时,特别注意头文件和库文件也必须是对应 ARM 架构的,通常通过 -I-L 指定交叉编译环境的 sysroot。


9. 总结

现在你应该对 GCC 的编译流程和常用选项有了清晰的认识。记住:

  • 四步走 :预处理(-E)、编译(-S)、汇编(-c)、链接(无选项)。

  • 多文件:可一次编译,也可分开编译再链接。

  • :动态库(.so)运行时需要;静态库(.a)直接嵌入可执行文件。

  • 查错-v-E -dMecho ... | gcc -E -v - 能帮你看到编译器的内部行为。

遇到问题就回来翻一翻,对照着命令行尝试,很快就能熟练掌握。祝编译顺利!

相关推荐
安迪西嵌入式3 天前
如何在VS Code中配置GCC编译器
vscode·gcc·mingw64·msys
love530love1 个月前
Windows 下 GCC 编译器安装与排错实录
人工智能·windows·python·gcc·msys2·gtk·msys2 mingw 64
EleganceJiaBao1 个月前
【嵌入式】GNU/GCC vs LLVM/Clang
gnu·clang·gcc·llvm
番茄灭世神2 个月前
基于VScode的C/C++环境搭建
vscode·cmake·gcc·c\c++·llvm·工具链搭建
切糕师学AI2 个月前
GCC是什么?
编译器·gcc
硬汉嵌入式2 个月前
MDK AC5,AC6,GCC以及IAR在const局部变量存储位置的异同
gcc·const·iar·ac6·mdk·ac5
唐装鼠2 个月前
GCC/Clang 构造函数特性(deepseek)
gcc·构造函数特性
yao000373 个月前
LLVM是什么 之 我与AI的思想碰撞
编辑器·gnu·clang·gcc·llvm
Lenyiin3 个月前
《 Linux 修炼全景指南: 八 》别再碎片化学习!掌控 Linux 开发工具链:gcc、g++、GDB、Bash、Python 与工程化实践
linux·python·bash·gdb·gcc·g++·lenyiin