基本编译
gcc [input].c -o [output]
示例:
gcc hello.c -o hello # 将 hello.c 编译为可执行文件 hello
./hello # 运行程序
分步编译
-E
(生成 .i
文件) gcc -E hello.c -o hello.i
-S
(生成 .s
文件) gcc -S hello.i -o hello.s
-c
(生成 .o
文件) gcc -c hello.s -o hello.o
gcc hello.o -o hello
调试信息
-g
选项生成带调试信息的文件(供 GDB 使用):
gcc -g test.c -o test_debug
优化选项
-O1
, -O2
, -O3
表示优化级别(-O2
最常用):
gcc -O2 main.c -o optimized_program
警告选项
-Wall
启用所有常见警告-Werror
将警告视为错误gcc -Wall -Werror strict_code.c -o strict_program
链接库文件
-l
指定库名(如数学库 -lm
)-L
指定库文件路径gcc calc.c -lm -L/usr/local/lib -o calc
# 编译两个文件并链接
gcc main.c utils.c -o app
# 1. 编译为 .o 文件
gcc -c mylib.c -o mylib.o
# 2. 打包为静态库
ar rcs libmylib.a mylib.o
# 3. 使用静态库
gcc main.c -L. -lmylib -o static_app
# 1. 编译为位置无关代码(-fPIC)
gcc -fPIC -c mylib.c -o mylib.o
# 2. 生成动态库(-shared)
gcc -shared mylib.o -o libmylib.so
# 3. 使用动态库(需设置库路径)
gcc main.c -L. -lmylib -o dynamic_app
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH # 临时添加库路径
类比:准备食材
想象你在做一道复杂的菜,首先你需要把所有的食材准备好,比如切菜、洗菜、调味等。预处理阶段就像是这个准备食材的过程。
具体过程
预处理器(cpp
)会处理源代码文件中的预处理指令,比如 #include
、#define
和 #ifdef
等。它会将这些指令替换成实际的代码或数据。
#include
:将指定的头文件内容插入到当前文件中。#define
:定义宏,替换代码中的宏定义。#ifdef
/ #ifndef
:条件编译,根据条件决定是否包含某段代码。示例
#include
#define PI 3.14159
int main() {
printf("The value of PI is %f", PI);
return 0;
}
预处理后,#include
会被替换成 stdio.h
文件的内容,PI
会被替换成 3.14159
。
类比:编写食谱
现在你已经准备好了食材,接下来你需要编写一个详细的食谱,告诉厨师每一步该怎么做。编译阶段就像是这个编写食谱的过程。
具体过程
编译器(gcc
)会将预处理后的源代码转换成汇编代码。这个过程会检查语法错误、类型检查等。
示例
预处理后的代码会被编译成类似以下的汇编代码(具体内容取决于编译器和平台):
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, OFFSET FLAT:.LC0
call puts@PLT
mov eax, 0
leave
ret
类比:将食谱翻译成操作步骤
现在你已经有了详细的食谱,接下来需要将这些食谱翻译成具体的操作步骤,让厨师可以一步步执行。汇编阶段就像是这个翻译过程。
具体过程
汇编器(as
)会将汇编代码转换成机器码,生成目标文件(.o
文件)。这个过程不会进行任何优化,只是简单地将汇编指令转换成机器码。
示例
上面的汇编代码会被转换成机器码,生成一个目标文件 main.o
。
类比:将多个菜谱合并成一本完整的烹饪书
现在你已经有了每道菜的具体操作步骤,接下来需要将这些步骤合并成一本完整的烹饪书,确保所有的步骤和食材都能协调工作。链接阶段就像是这个合并过程。
具体过程
链接器(ld
)会将多个目标文件和库文件合并成一个可执行文件。这个过程会解析所有的符号引用,确保每个函数和变量的调用都能找到正确的定义。
假设你有两个源文件 main.c
和 utils.c
,编译后会生成 main.o
和 utils.o
。链接器会将这两个目标文件合并成一个可执行文件 program
。
#include
、#define
等指令。定义:静态库是将一系列的目标文件(.o
文件)打包成一个单独的文件,在编译链接时,链接器会把静态库中被程序使用的代码直接复制到最终的可执行文件中。静态库的文件扩展名通常是 .a
静态连接:
在静态连接中,编译器和链接器在编译阶段就把程序所需要使用的库代码(静态库)直接合并到最终生成的可执行文件中。
add.c
和 sub.c
,分别实现加法和减法功能。// add.c
int add(int a, int b) {
return a + b;
}
// sub.c
int sub(int a, int b) {
return a - b;
}
使用 `gcc` 编译这两个源文件生成目标文件:
gcc -c add.c sub.c
然后使用 `ar` 工具创建静态库:
ar rcs libmath.a add.o sub.o
这里 ar
是归档工具,r
表示将目标文件插入到归档文件中,c
表示如果归档文件不存在则创建它,s
表示写入一个目标文件索引。
这样来使用静态库
gcc main.c -L. -lmath -o main
-L.
表示在当前目录下查找库文件,-lmath
表示链接名为 libmath.a
的静态库(注意,-l
后面跟的是库名去掉 lib
前缀和 .a
后缀)。
定义:动态库是在程序运行时才被加载到内存中供程序使用的库。多个程序可以共享同一个动态库的内存副本。是C/C++或者其他第三方软件提供的所有方法的集合,被所有程序以链接的方式关联起来。动态库的文件扩展名通常是 .so
动态链接:
在动态连接中,编译阶段只是记录程序需要使用哪些动态库(.so
文件),然后再需要时加载,并不会把库代码复制到可执行文件中。
add.c
和 sub.c
源文件,先编译成目标文件:gcc -c -fPIC add.c sub.c
-fPIC
表示生成位置无关代码,这是创建动态库所必需的。
使用 gcc
创建动态库:
gcc -shared -o libmath.so add.o sub.o
-shared
表示生成共享库。
使用动态库:
gcc main.c -L. -lmath -o main
编译命令和静态库类似,但在运行可执行文件时,需要确保系统能找到动态库。可以通过设置 LD_LIBRARY_PATH
环境变量来指定动态库的搜索路径:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./main
注意:linux系统中认为后缀无意义,但不是代表gcc认为后缀无意义,也就是说我们后缀不对的话gcc编译会出错的。