最简单的编译和链接 (单个 C 文件)
假设你有一个名为 hello.c 的 C 语言源文件:

hello.c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
要将其编译成可执行文件,最简单的命令是:
gcc hello.c
执行过程详解:
当你输入这个命令后,gcc 会自动执行以下步骤:

- 预处理:处理
#include、#define等预处理指令,它会将stdio.h的内容展开到hello.c中,并生成一个临时文件(hello.i)。 - 编译:将预处理后的代码(
.i文件)转换成汇编代码,这步会进行语法分析、词法分析、语义分析等,最终生成一个汇编文件(hello.s)。 - 汇编:将汇编代码(
.s文件)转换成机器语言,生成目标文件(Object File,.o文件)。hello.o,这个文件包含了机器码,但还不能直接运行,因为它可能还缺少其他部分。 - 链接:这是最后一步。
gcc会将你的hello.o文件与 C 标准库(printf函数的实现就在里面)以及其他必要的文件链接起来,生成一个最终的可执行文件。
默认生成的文件名:
在 Linux 系统上,gcc 默认会生成一个名为 a.out 的可执行文件。
执行程序:
你可以使用 来运行当前目录下的可执行文件:

./a.out
输出:
Hello, World!
指定输出文件名
默认的 a.out 文件名很不直观,我们可以使用 -o 选项来指定输出的可执行文件名。
gcc hello.c -o hello
这个命令告诉 gcc 将编译和链接后的最终结果保存为 hello 文件。
你可以这样运行它:
./hello
输出和之前一样。
分步编译 (推荐用于大型项目)
理解分步编译对于调试大型项目非常重要。
第一步:只编译,不链接
使用 -c 选项,gcc 只会执行到“汇编”步骤,生成 .o 目标文件,而不会进行链接。
gcc -c hello.c -o hello.o
执行后,你会得到一个 hello.o 文件,你可以用 file 命令查看它的类型:
file hello.o
输出会类似这样,表明它是一个可重定位的目标文件:
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
第二步:链接
当你有了所有目标文件后(比如还有 main.o, utils.o 等),你可以将它们链接成一个可执行文件。
gcc hello.o -o hello
这个命令会链接 hello.o 和所需的 C 库,生成最终的 hello 可执行文件。
常用编译选项
gcc 的强大之处在于其丰富的选项。
| 选项 | 全称 | 描述 | 示例 |
|---|---|---|---|
-o |
--output= |
指定输出文件名。 | gcc -o myapp main.c utils.c |
-c |
只编译,不链接,生成 .o 文件。 |
gcc -c main.c -o main.o |
|
-I |
--include-dir= |
添加头文件搜索路径,当你把头文件放在非标准目录时使用。 | gcc -I /path/to/my/headers main.c |
-L |
--library-path= |
添加库文件搜索路径。 | gcc -L /path/to/my/libs myapp.c -lmath |
-l |
--library= |
链接指定的库,注意库名不带 lib 前缀和 .so/.a 后缀。 |
gcc myapp.c -lm (链接 math 库) |
-g |
包含调试信息,使用 GDB 调试时必须的选项。 | gcc -g main.c -o myapp |
|
-O0, -O1, -O2, -O3 |
优化级别。-O0 不优化(最快编译),-O3 最高优化(最慢编译,性能最好)。-O2 是最常用的平衡点。 |
gcc -O2 main.c -o myapp |
|
-Wall |
--warnings=all |
开启所有常见警告,强烈推荐始终使用! | gcc -Wall main.c -o myapp |
-Wextra |
开启额外的警告,比 -Wall 更严格。 |
gcc -Wall -Wextra main.c -o myapp |
|
-std= |
--standard= |
指定 C 语言标准,如 c89, c99, c11, c17 (或 gnu89, gnu99 等)。 |
gcc -std=c11 main.c -o myapp |
-D |
--define-macro= |
在编译时定义宏,相当于在代码中写 #define。 |
gcc -DDEBUG=1 main.c -o myapp |
-E |
只进行预处理,输出到标准输出,通常用于调试预处理过程。 | gcc -E hello.c |
|
-S |
只进行到编译阶段,生成汇编代码(.s 文件)。 |
gcc -S hello.c |
综合实例
假设你的项目结构如下:
my_project/
├── src/
│ ├── main.c
│ └── utils.c
├── include/
│ └── utils.h
└── Makefile (稍后介绍)
-
include/utils.h#ifndef UTILS_H #define UTILS_H int add(int a, int b); #endif
-
src/utils.c#include "utils.h" int add(int a, int b) { return a + b; } -
src/main.c#include <stdio.h> #include "../include/utils.h" // 注意路径 int main() { int result = add(5, 3); printf("The result is: %d\n", result); return 0; }
编译命令:
我们需要:
- 为
main.c和utils.c分别生成.o文件。 - 链接这两个
.o文件。
# 1. 编译 main.c,指定头文件搜索路径为 include 目录 gcc -c src/main.c -I include -o main.o # 2. 编译 utils.c gcc -c src/utils.c -I include -o utils.o # 3. 链接 main.o 和 utils.o,生成最终可执行文件 app gcc main.o utils.o -o app # 4. 运行 ./app
输出:
The result is: 8
自动化编译:make 和 Makefile
手动输入上述命令很繁琐,尤其是在项目文件很多时。make 是一个自动化工具,通过读取 Makefile 文件来管理项目的编译。
下面是为上面项目编写的简单 Makefile:
# 定义变量
CC = gcc
CFLAGS = -Wall -Wextra -I include -std=c11
TARGET = app
SRCS = src/main.c src/utils.c
OBJS = $(SRCS:.c=.o)
# 默认目标
all: $(TARGET)
# 链接规则
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# 隐式规则:如何从 .c 文件生成 .o 文件
# make 内置了这个规则,我们这里只是展示
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理生成的文件
clean:
rm -f $(OBJS) $(TARGET)
使用 make:
你只需要在项目根目录下运行:
make
make 会自动读取 Makefile,并根据规则执行编译命令,它会输出类似这样的信息:
gcc -Wall -Wextra -I include -std=c11 -c src/main.c -o main.o
gcc -Wall -Wextra -I include -std=c11 -c src/utils.c -o utils.o
gcc main.o utils.o -o app
要清理所有编译产物,运行:
make clean
| 任务 | 命令 |
|---|---|
| 快速编译单个文件 | gcc source.c |
| 指定输出文件名 | gcc source.c -o my_program |
| 生成目标文件 (不链接) | gcc -c source.c -o source.o |
| 链接目标文件 | gcc main.o utils.o -o my_program |
| 开启警告和调试信息 | gcc -g -Wall source.c -o my_program |
| 添加头文件/库路径 | gcc -I /path/to/headers -L /path/to/libs source.c -lmylib -o my_program |
| 自动化编译 | make (需要 Makefile) |
掌握 gcc 命令是成为 C/C++ 开发者的必备技能,从 -o, -c, -Wall 开始,逐步学习其他选项,你会发现它非常强大和灵活。
