Linux 下 gcc / g++ 编译过程详解:从编译到链接

前言

在 Linux 下学习 C / C++,一定绕不开两个编译命令:

复制代码
gcc

和:

复制代码
g++

很多初学者第一次接触 Linux 编译 C 语言程序时,可能会看到这样的命令:

复制代码
gcc main.c

执行之后,当前目录下会生成一个文件:

复制代码
a.out

这个 a.out 就是编译生成的可执行程序。

运行它:

复制代码
./a.out

如果程序没有问题,就可以看到程序的运行结果。

但是很多人刚开始学习时,会对这些问题感到疑惑:

复制代码
gcc 是什么?
g++ 又是什么?
gcc main.c 为什么会生成 a.out?
a.out 是什么?
为什么运行程序要写 ./a.out?
gcc -o main main.c 是什么意思?
-o 后面的 main 是输入文件还是输出文件?
gcc 编译程序时到底经历了哪些步骤?
什么是预处理、编译、汇编、链接?
为什么有时候会出现 undefined reference?
为什么找不到头文件?

本文就从最简单的 gcc main.c 开始,按照从浅到深、从简单到复杂的顺序,带你完整理解 Linux 下 gcc / g++ 从源代码到可执行程序的整个过程。


一、gcc 和 g++ 是什么?

1. gcc 是什么?

gcc 原本表示 GNU C Compiler,也就是 GNU C 语言编译器。

现在的 gcc 通常指 GNU Compiler Collection,也就是 GNU 编译器套件。

它不仅可以编译 C 语言,也可以支持 C++、Objective-C、Fortran 等多种语言。

不过在刚开始学习时,可以先简单理解为:

复制代码
gcc 主要用于编译 C 语言程序

例如有一个 C 源文件:

复制代码
main.c

就可以使用:

复制代码
gcc main.c

来编译它。


2. g++ 是什么?

g++ 是 GNU C++ 编译器,主要用于编译 C++ 程序。

例如有一个 C++ 源文件:

复制代码
main.cpp

就可以使用:

复制代码
g++ main.cpp

来编译它。


3. gcc 和 g++ 的区别

简单记忆:

复制代码
.c 文件一般使用 gcc 编译
.cpp 文件一般使用 g++ 编译

例如:

复制代码
gcc main.c

用于编译 C 程序。

复制代码
g++ main.cpp

用于编译 C++ 程序。

对于 C++ 程序,建议使用 g++,因为 g++ 会自动按照 C++ 的方式进行编译和链接,并且会自动链接 C++ 标准库。

例如下面这个 C++ 程序:

复制代码
#include <iostream>
using namespace std;

int main()
{
    cout << "Hello C++" << endl;
    return 0;
}

推荐使用:

复制代码
g++ main.cpp

如果使用:

复制代码
gcc main.cpp

可能会出现类似下面的链接错误:

复制代码
undefined reference to `std::cout'
undefined reference to `std::endl'

原因是 gcc 默认不会像 g++ 那样自动链接 C++ 标准库。

所以初学阶段可以先记住一句话:

复制代码
写 C 用 gcc,写 C++ 用 g++。

二、从最简单的命令开始:gcc main.c

先准备一个最简单的 C 程序。

文件名:

复制代码
main.c

代码内容如下:

复制代码
#include <stdio.h>

int main()
{
    printf("Hello gcc\n");
    return 0;
}

现在使用 gcc 编译:

复制代码
gcc main.c

这条命令的意思是:

复制代码
使用 gcc 编译 main.c 这个源文件

执行完成后,查看当前目录:

复制代码
ls

可以看到类似结果:

复制代码
a.out  main.c

可以发现,执行 gcc main.c 之后,系统生成了一个新的文件:

复制代码
a.out

这个 a.out 就是编译出来的可执行程序。

运行它:

复制代码
./a.out

输出结果:

复制代码
Hello gcc

三、为什么生成的文件叫 a.out?

当我们执行:

复制代码
gcc main.c

时,只告诉了 gcc 一件事情:

复制代码
我要编译 main.c

但是我们没有告诉 gcc:

复制代码
最终生成的可执行文件叫什么名字

所以 gcc 会使用默认的输出文件名:

复制代码
a.out

也就是说:

复制代码
gcc main.c

可以理解为:

复制代码
编译 main.c,并默认生成名为 a.out 的可执行文件

a.out 是 Unix / Linux 系统中比较传统的默认可执行文件名。


四、为什么运行程序要写 ./a.out?

在 Windows 中,我们可能习惯双击运行程序。

但是在 Linux 命令行中,要运行当前目录下的程序,通常需要写:

复制代码
./程序名

所以运行 a.out 时,需要写:

复制代码
./a.out

这里的:

复制代码
.   表示当前目录
/   表示路径分隔符

所以:

复制代码
./a.out

表示:

复制代码
运行当前目录下的 a.out 文件

如果直接输入:

复制代码
a.out

可能会提示:

复制代码
command not found

原因是 Linux 默认不会直接从当前目录查找可执行程序。


五、使用 -o 指定输出文件名

每次编译都生成 a.out 并不方便。

例如我们希望生成的可执行文件名叫:

复制代码
main

就可以使用 -o 参数。

命令如下:

复制代码
gcc -o main main.c

这条命令的意思是:

复制代码
使用 gcc 编译 main.c,并把生成的可执行文件命名为 main

其中:

复制代码
-o main

表示:

复制代码
指定输出文件名为 main

后面的:

复制代码
main.c

表示:

复制代码
输入的源文件是 main.c

执行后查看当前目录:

复制代码
ls

可以看到:

复制代码
main  main.c

运行程序:

复制代码
./main

输出:

复制代码
Hello gcc

六、一定要理解:-o 后面跟的是输出文件名

-o 是 gcc / g++ 中非常常用的参数,它的作用是指定输出文件名。

例如:

复制代码
gcc -o main main.c

可以拆开理解为:

复制代码
gcc       使用 gcc 编译器
-o main   指定输出文件名为 main
main.c    输入源文件是 main.c

这里一定要注意:

复制代码
-o 后面的 main 是输出文件名,不是源文件名。

所以,下面这种写法是非常危险的:

复制代码
gcc -o main.c main.c

这条命令的意思不是"编译 main.c"。

它的真实含义是:

复制代码
把 main.c 编译后的结果输出成 main.c

也就是说,输出文件名和源文件名重名了。

这样可能会导致原来的 main.c 源代码被覆盖成一个二进制可执行文件。

此时看起来就像:

复制代码
源文件被删了

但更准确地说,是:

复制代码
源文件被编译生成的二进制文件覆盖了

所以一定要记住:

复制代码
不要把 -o 后面的输出文件名写成已有的源文件名。

正确写法:

复制代码
gcc -o main main.c

错误且危险的写法:

复制代码
gcc -o main.c main.c

七、gcc -o main main.c 和 gcc main.c -o main 的区别

下面两种写法都可以:

复制代码
gcc -o main main.c

也可以写成:

复制代码
gcc main.c -o main

它们的作用是一样的,都是:

复制代码
编译 main.c,并生成名为 main 的可执行文件

但是对于初学者来说,更推荐先使用:

复制代码
gcc -o main main.c

因为这种写法更容易理解:

复制代码
gcc       编译器
-o main   输出文件名
main.c    输入源文件

而:

复制代码
gcc main.c -o main

虽然也正确,但是初学者容易把 main.cmain 的关系看混。

所以本文后续主要采用:

复制代码
gcc -o main main.c

作为示例。


八、gcc 常见命令格式

gcc 最基础的使用格式是:

复制代码
gcc 源文件

例如:

复制代码
gcc main.c

表示:

复制代码
编译 main.c,默认生成 a.out

如果想指定输出文件名,可以写成:

复制代码
gcc -o 输出文件名 源文件

例如:

复制代码
gcc -o main main.c

表示:

复制代码
编译 main.c,生成可执行文件 main

多个源文件也可以一起编译:

复制代码
gcc -o main main.c add.c

表示:

复制代码
编译 main.c 和 add.c,最后生成可执行文件 main

九、gcc main.c 背后到底做了什么?

虽然我们只执行了一条命令:

复制代码
gcc main.c

但是 gcc 在背后并不是只做了一件事。

它实际上完成了四个主要阶段:

复制代码
预处理 -> 编译 -> 汇编 -> 链接

完整过程可以理解为:

复制代码
main.c
  ↓ 预处理
main.i
  ↓ 编译
main.s
  ↓ 汇编
main.o
  ↓ 链接
a.out

如果指定输出文件名:

复制代码
gcc -o main main.c

那么最终生成的就是:

复制代码
main

整体过程就是:

复制代码
main.c
  ↓ 预处理
main.i
  ↓ 编译
main.s
  ↓ 汇编
main.o
  ↓ 链接
main

简单来说:

复制代码
gcc main.c

或者:

复制代码
gcc -o main main.c

这一条命令背后其实自动完成了:

复制代码
预处理 + 编译 + 汇编 + 链接

下面我们一步一步拆开来看。


十、第一步:预处理

预处理是整个编译过程的第一步。

可以使用 -E 参数让 gcc 只执行预处理:

复制代码
gcc -E main.c -o main.i

这条命令的意思是:

复制代码
只对 main.c 进行预处理,并把结果输出到 main.i

预处理阶段主要做这些事情:

复制代码
1. 展开头文件
2. 替换宏定义
3. 处理条件编译
4. 删除注释

例如有这样一段代码:

复制代码
#include <stdio.h>

#define MESSAGE "Hello gcc"

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

其中:

复制代码
#include <stdio.h>

会在预处理阶段被展开。

宏定义:

复制代码
#define MESSAGE "Hello gcc"

会被替换。

所以:

复制代码
printf("%s\n", MESSAGE);

经过预处理后,大致会变成:

复制代码
printf("%s\n", "Hello gcc");

预处理之后生成的文件通常使用 .i 后缀:

复制代码
main.i

这个文件依然是 C 代码,只是已经完成了头文件展开、宏替换、条件编译处理等工作。


十一、第二步:编译

编译阶段会把预处理后的 C 代码转换成汇编代码。

可以使用 -S 参数让 gcc 只编译到汇编阶段:

复制代码
gcc -S main.i -o main.s

也可以直接从 C 源文件生成汇编文件:

复制代码
gcc -S main.c -o main.s

执行后会生成:

复制代码
main.s

.s 文件里面是汇编代码。

内容可能类似这样:

复制代码
	.file	"main.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp

汇编代码已经比 C 语言更接近 CPU 能理解的指令了。

但是它仍然不是最终可以直接运行的程序。


十二、第三步:汇编

汇编阶段会把汇编代码转换成目标文件。

可以使用 -c 参数生成目标文件:

复制代码
gcc -c main.s -o main.o

也可以直接从 C 源文件生成目标文件:

复制代码
gcc -c main.c -o main.o

执行后会生成:

复制代码
main.o

.o 文件叫做目标文件,也叫 object file。

它已经包含了机器指令,但是还不能直接运行。

例如你不能直接这样运行:

复制代码
./main.o

因为 main.o 只是一个中间文件,还没有完成最后的链接。


十三、第四步:链接

链接是生成最终可执行程序的最后一步。

如果已经有了目标文件:

复制代码
main.o

可以使用下面的命令进行链接:

复制代码
gcc -o main main.o

这条命令表示:

复制代码
把 main.o 链接成最终可执行文件 main

执行后生成:

复制代码
main

运行:

复制代码
./main

输出:

复制代码
Hello gcc

链接阶段主要做这些事情:

复制代码
1. 合并多个目标文件
2. 解析函数和变量的引用关系
3. 链接标准库或第三方库
4. 生成最终可执行文件

例如程序中使用了:

复制代码
printf("Hello gcc\n");

但是 printf 的真正实现并不在我们自己写的 main.c 里面,而是在 C 标准库中。

在链接阶段,链接器会找到 printf 的实现,并把它和我们的程序关联起来,最终生成可以运行的可执行文件。


十四、完整分步骤编译过程

原本一条命令:

复制代码
gcc -o main main.c

可以拆成下面四步:

复制代码
# 1. 预处理:main.c -> main.i
gcc -E main.c -o main.i

# 2. 编译:main.i -> main.s
gcc -S main.i -o main.s

# 3. 汇编:main.s -> main.o
gcc -c main.s -o main.o

# 4. 链接:main.o -> main
gcc -o main main.o

最终运行:

复制代码
./main

输出:

复制代码
Hello gcc

也就是说:

复制代码
gcc -o main main.c

等价于 gcc 帮我们自动完成了:

复制代码
预处理 main.c 生成 main.i
编译 main.i 生成 main.s
汇编 main.s 生成 main.o
链接 main.o 生成 main

平时我们通常不需要手动拆开这四步,因为 gcc 会自动完成。

但是理解这四步非常重要,因为很多编译错误、链接错误都和这些阶段有关。


十五、C++ 程序的编译过程

C++ 程序的编译过程和 C 程序类似,也分为:

复制代码
预处理 -> 编译 -> 汇编 -> 链接

假设有一个 C++ 文件:

复制代码
main.cpp

代码如下:

复制代码
#include <iostream>
using namespace std;

int main()
{
    cout << "Hello g++" << endl;
    return 0;
}

最简单的编译方式:

复制代码
g++ main.cpp

默认也会生成:

复制代码
a.out

运行:

复制代码
./a.out

输出:

复制代码
Hello g++

如果想指定输出文件名:

复制代码
g++ -o main main.cpp

运行:

复制代码
./main

输出:

复制代码
Hello g++

C++ 也可以分步骤编译:

复制代码
# 1. 预处理:main.cpp -> main.ii
g++ -E main.cpp -o main.ii

# 2. 编译:main.ii -> main.s
g++ -S main.ii -o main.s

# 3. 汇编:main.s -> main.o
g++ -c main.s -o main.o

# 4. 链接:main.o -> main
g++ -o main main.o

C++ 预处理后的文件通常可以使用:

复制代码
.ii

作为后缀。

C 语言预处理后的文件通常使用:

复制代码
.i

作为后缀。


十六、头文件和源文件的关系

很多初学者会把头文件和源文件混在一起。

先看一个简单例子。

项目结构如下:

复制代码
project/
├── add.h
├── add.c
└── main.c

add.h 内容如下:

复制代码
#ifndef ADD_H
#define ADD_H

int add(int a, int b);

#endif

add.c 内容如下:

复制代码
#include "add.h"

int add(int a, int b)
{
    return a + b;
}

main.c 内容如下:

复制代码
#include <stdio.h>
#include "add.h"

int main()
{
    int ret = add(10, 20);
    printf("ret = %d\n", ret);
    return 0;
}

其中:

复制代码
int add(int a, int b);

是函数声明。

它的作用是告诉编译器:

复制代码
有一个函数叫 add
它接收两个 int 参数
它返回一个 int 类型的结果

但是这只是声明,不是实现。

真正的实现是在 add.c 里面:

复制代码
int add(int a, int b)
{
    return a + b;
}

所以可以简单理解为:

复制代码
头文件 .h:主要放声明
源文件 .c:主要放实现

十七、多文件直接编译

对于上面的项目,可以直接这样编译:

复制代码
gcc -o main main.c add.c

这条命令的意思是:

复制代码
同时编译 main.c 和 add.c,并链接生成可执行文件 main

运行:

复制代码
./main

输出:

复制代码
ret = 30

如果只编译:

复制代码
gcc -o main main.c

可能会出现:

复制代码
undefined reference to `add'

原因是 main.c 里面调用了 add 函数,但是 add 函数的实现写在 add.c 里面。

如果编译命令中没有带上 add.c,链接器就找不到 add 的实现。

所以正确写法是:

复制代码
gcc -o main main.c add.c

十八、多文件分步编译

大型项目中,通常不会每次都把所有 .c 文件从头编译一遍。

更常见的方式是先分别生成 .o 目标文件,再统一链接。

例如:

复制代码
gcc -c main.c -o main.o
gcc -c add.c -o add.o

这两条命令分别生成:

复制代码
main.o
add.o

然后再链接:

复制代码
gcc -o main main.o add.o

运行:

复制代码
./main

输出:

复制代码
ret = 30

这种方式的好处是:

复制代码
如果只修改了 add.c,只需要重新编译 add.c
不需要重新编译 main.c
最后重新链接即可

例如:

复制代码
gcc -c add.c -o add.o
gcc -o main main.o add.o

这也是 Makefile 和大型项目构建系统的基础。


十九、头文件路径:-I

如果头文件不在当前目录,而是在单独的 include 目录中,就需要使用 -I 参数指定头文件搜索路径。

例如项目结构如下:

复制代码
project/
├── include/
│   └── add.h
└── src/
    ├── add.c
    └── main.c

此时 main.c 中可能这样写:

复制代码
#include "add.h"

但是编译时如果直接写:

复制代码
gcc -o main src/main.c src/add.c

可能会报错:

复制代码
fatal error: add.h: No such file or directory

意思是:

复制代码
找不到 add.h 这个头文件

这时候需要告诉 gcc 去哪里找头文件:

复制代码
gcc -o main src/main.c src/add.c -I include

其中:

复制代码
-I include

表示:

复制代码
把 include 目录加入头文件搜索路径

也可以写成:

复制代码
gcc -I include -o main src/main.c src/add.c

-I 的位置一般比较灵活,但要保证路径写正确。


二十、库文件是什么?

头文件只提供声明,真正的函数实现可能来自源文件,也可能来自库文件。

库文件可以简单理解为:

复制代码
已经提前编译好的代码集合

Linux 下常见库文件有两类:

复制代码
静态库:.a
动态库:.so

例如:

复制代码
libm.a
libm.so
libpthread.so

头文件和库文件的区别可以这样理解:

复制代码
头文件:告诉编译器函数长什么样
库文件:告诉链接器函数真正在哪里

例如:

复制代码
#include <math.h>

math.h 只是提供数学函数的声明。

而数学函数真正的实现,需要在链接阶段链接数学库。


二十一、链接库:-L 和 -l

编译程序时,如果需要链接第三方库,常见参数有两个:

复制代码
-L:指定库文件所在目录
-l:指定要链接的库名

例如:

复制代码
gcc -o main main.c -L ./lib -lxxx

其中:

复制代码
-L ./lib

表示:

复制代码
到 ./lib 目录下查找库文件

而:

复制代码
-lxxx

表示:

复制代码
链接名为 xxx 的库

这里要注意,-lxxx 并不是去找一个叫 xxx 的文件。

它实际会去找类似这样的库文件:

复制代码
libxxx.so
libxxx.a

也就是说:

复制代码
-lm

实际对应的可能是:

复制代码
libm.so
libm.a

-lpthread

实际对应的可能是:

复制代码
libpthread.so
libpthread.a

二十二、数学库示例:-lm

看下面这个程序:

复制代码
#include <stdio.h>
#include <math.h>

int main()
{
    double ret = sqrt(16.0);
    printf("%f\n", ret);
    return 0;
}

文件名:

复制代码
main.c

如果直接编译:

复制代码
gcc -o main main.c

在一些环境下可能会出现链接错误:

复制代码
undefined reference to `sqrt'

原因是:

复制代码
sqrt 函数的声明在 math.h 中
但 sqrt 函数的实现需要链接数学库 libm

正确写法:

复制代码
gcc -o main main.c -lm

其中:

复制代码
-lm

表示链接数学库。

需要注意,库参数通常建议放在源文件或目标文件后面。

推荐:

复制代码
gcc -o main main.c -lm

不推荐:

复制代码
gcc -lm -o main main.c

因为在某些链接器规则下,库的顺序会影响符号解析。


二十三、编译错误和链接错误的区别

学习 gcc / g++ 时,一定要区分:

复制代码
编译错误
链接错误

1. 编译错误

编译错误通常发生在源代码变成目标文件之前。

常见原因有:

复制代码
语法错误
变量未声明
类型错误
找不到头文件
函数声明不匹配

例如:

复制代码
#include "add.h"

但是编译器找不到 add.h,就会出现:

复制代码
fatal error: add.h: No such file or directory

这是编译阶段的问题。


2. 链接错误

链接错误通常发生在 .o 目标文件生成之后,链接成可执行文件的时候。

常见错误:

复制代码
undefined reference to `xxx'

例如:

复制代码
undefined reference to `add'

这说明:

复制代码
编译器知道 add 这个函数存在声明
但是链接器找不到 add 函数的真正实现

比如只写了:

复制代码
gcc -o main main.c

但是没有把 add.c 一起编译进去,就可能出现这个错误。

正确写法:

复制代码
gcc -o main main.c add.c

或者:

复制代码
gcc -c main.c -o main.o
gcc -c add.c -o add.o
gcc -o main main.o add.o

二十四、常见 gcc 参数总结

1. -o:指定输出文件名

复制代码
gcc -o main main.c

表示:

复制代码
编译 main.c,生成可执行文件 main

不要写成:

复制代码
gcc -o main.c main.c

因为这样可能会覆盖源文件。


2. -E:只进行预处理

复制代码
gcc -E main.c -o main.i

表示:

复制代码
只进行预处理,不编译、不汇编、不链接

3. -S:只生成汇编文件

复制代码
gcc -S main.c -o main.s

表示:

复制代码
生成汇编文件 main.s

4. -c:只生成目标文件

复制代码
gcc -c main.c -o main.o

表示:

复制代码
生成目标文件 main.o,不进行链接

5. -I:指定头文件搜索路径

复制代码
gcc -I include -o main src/main.c src/add.c

表示:

复制代码
让 gcc 到 include 目录下查找头文件

6. -L:指定库文件搜索路径

复制代码
gcc -o main main.c -L ./lib -lxxx

表示:

复制代码
让链接器到 ./lib 目录下查找库文件

7. -l:指定链接库

复制代码
gcc -o main main.c -lm

表示:

复制代码
链接数学库 libm

8. -Wall:开启常见警告

复制代码
gcc -Wall -o main main.c

表示:

复制代码
开启常见编译警告

建议平时写代码时加上。


9. -Wextra:开启更多警告

复制代码
gcc -Wall -Wextra -o main main.c

表示:

复制代码
在 -Wall 的基础上开启更多警告

10. -g:生成调试信息

复制代码
gcc -g -o main main.c

表示:

复制代码
生成调试信息,方便使用 gdb 调试

例如:

复制代码
gdb ./main

11. -O:开启优化

常见优化等级:

复制代码
-O0
-O1
-O2
-O3

开发调试阶段常用:

复制代码
gcc -g -O0 -o main main.c

发布程序时可以使用:

复制代码
gcc -O2 -o main main.c

12. -std:指定语言标准

C 语言示例:

复制代码
gcc -std=c11 -o main main.c

C++ 示例:

复制代码
g++ -std=c++17 -o main main.cpp

常见 C++ 标准:

复制代码
c++11
c++14
c++17
c++20
c++23

二十五、gcc 常用命令总结

复制代码
# 编译 C 程序,默认生成 a.out
gcc main.c

# 运行默认生成的程序
./a.out

# 指定输出文件名
gcc -o main main.c

# 运行指定名称的程序
./main

# 开启常见警告
gcc -Wall -o main main.c

# 开启更多警告
gcc -Wall -Wextra -o main main.c

# 生成调试版本
gcc -g -O0 -o main main.c

# 生成优化版本
gcc -O2 -o main main.c

# 指定 C 标准
gcc -std=c11 -o main main.c

# 只预处理
gcc -E main.c -o main.i

# 只生成汇编
gcc -S main.c -o main.s

# 只生成目标文件
gcc -c main.c -o main.o

# 根据目标文件链接生成可执行文件
gcc -o main main.o

# 多文件直接编译
gcc -o main main.c add.c

# 多文件分步编译
gcc -c main.c -o main.o
gcc -c add.c -o add.o
gcc -o main main.o add.o

# 指定头文件路径
gcc -I include -o main src/main.c src/add.c

# 指定库路径并链接库
gcc -o main main.c -L ./lib -lxxx

# 链接数学库
gcc -o main main.c -lm

二十六、g++ 常用命令总结

复制代码
# 编译 C++ 程序,默认生成 a.out
g++ main.cpp

# 运行默认生成的程序
./a.out

# 指定输出文件名
g++ -o main main.cpp

# 运行指定名称的程序
./main

# 开启常见警告
g++ -Wall -o main main.cpp

# 开启更多警告
g++ -Wall -Wextra -o main main.cpp

# 生成调试版本
g++ -g -O0 -o main main.cpp

# 生成优化版本
g++ -O2 -o main main.cpp

# 指定 C++ 标准
g++ -std=c++17 -o main main.cpp

# 只预处理
g++ -E main.cpp -o main.ii

# 只生成汇编
g++ -S main.cpp -o main.s

# 只生成目标文件
g++ -c main.cpp -o main.o

# 根据目标文件链接生成可执行文件
g++ -o main main.o

# 多文件直接编译
g++ -o main main.cpp add.cpp

# 多文件分步编译
g++ -c main.cpp -o main.o
g++ -c add.cpp -o add.o
g++ -o main main.o add.o

# 指定头文件路径
g++ -I include -o main src/main.cpp src/add.cpp

# 指定库路径并链接库
g++ -o main main.cpp -L ./lib -lxxx

二十七、推荐的编译命令

对于 C 语言学习阶段,推荐使用:

复制代码
gcc -Wall -Wextra -g -O0 -o main main.c

含义:

复制代码
-Wall      开启常见警告
-Wextra    开启更多警告
-g         生成调试信息
-O0        不进行优化,方便调试
-o main    输出文件名为 main
main.c     输入源文件

对于 C++ 学习阶段,推荐使用:

复制代码
g++ -std=c++17 -Wall -Wextra -g -O0 -o main main.cpp

含义:

复制代码
-std=c++17 指定 C++17 标准
-Wall      开启常见警告
-Wextra    开启更多警告
-g         生成调试信息
-O0        不进行优化,方便调试
-o main    输出文件名为 main
main.cpp   输入源文件

发布程序时可以使用优化选项:

复制代码
gcc -O2 -o main main.c

或者:

复制代码
g++ -std=c++17 -O2 -o main main.cpp

二十八、几个核心概念总结

1. gcc main.c 会默认生成 a.out

复制代码
gcc main.c

默认生成:

复制代码
a.out

运行:

复制代码
./a.out

2. 使用 -o 可以指定输出文件名

复制代码
gcc -o main main.c

表示:

复制代码
编译 main.c,生成 main

3. -o 后面是输出文件名

一定不要写成:

复制代码
gcc -o main.c main.c

因为这样可能会把源文件 main.c 覆盖掉。


4. 一条 gcc 命令背后有四个阶段

复制代码
预处理 -> 编译 -> 汇编 -> 链接

对应文件变化:

复制代码
main.c -> main.i -> main.s -> main.o -> main

5. 头文件不是库文件

头文件主要放声明:

复制代码
函数声明
宏定义
类型定义
结构体声明
类声明

库文件或源文件中才有真正的实现。


6. 编译错误和链接错误不是一回事

编译错误常见于:

复制代码
语法错误
找不到头文件
类型错误
声明错误

链接错误常见于:

复制代码
undefined reference
找不到函数实现
找不到库文件

7. C 用 gcc,C++ 用 g++

复制代码
gcc -o main main.c

用于 C 程序。

复制代码
g++ -o main main.cpp

用于 C++ 程序。


结语

刚开始学习 Linux 下 C / C++ 编译时,不要一上来就死记复杂命令。

可以先从最简单的命令开始:

复制代码
gcc main.c

理解它会默认生成:

复制代码
a.out

然后再学习如何指定输出文件名:

复制代码
gcc -o main main.c

接着再逐步理解这条命令背后的完整过程:

复制代码
main.c
  ↓ 预处理
main.i
  ↓ 编译
main.s
  ↓ 汇编
main.o
  ↓ 链接
main

当你理解了:

复制代码
预处理
编译
汇编
链接

这四个阶段之后,再看 Makefile、多文件编译、静态库、动态库、头文件路径、库文件链接等内容,就会清晰很多。

最终需要记住的核心命令是:

复制代码
gcc main.c

默认生成:

复制代码
a.out

指定输出文件名:

复制代码
gcc -o main main.c

C++ 程序:

复制代码
g++ -o main main.cpp

理解这些基础内容,是继续学习 Linux C / C++ 开发、Makefile、CMake、静态库、动态库以及大型项目构建的第一步。

相关推荐
C语言小火车14 分钟前
嵌入式Linux应用开发技术栈完全指南
linux·运维·服务器
天南散修1 小时前
MT7916驱动中802.11转换为802.3
linux·网络·驱动开发·wifi·802.11
CriticalThinking2 小时前
在xshell中使用ssh隧道访问远程服务
linux·网络·ssh
爱装代码的小瓶子2 小时前
安工大题目分类(含解析和翻译)
linux·网络·c
是个西兰花3 小时前
linux:命名管道与共享内存
linux·运维·服务器·网络·c++
Snasph3 小时前
Linux 日志流水线深度解析:syslog() → journald → rsyslog → /var/log/syslog
linux·syslog·rsyslog
凡人叶枫3 小时前
Effective C++ 条款08:别让异常逃离析构函数
java·linux·数据库·c++·嵌入式开发
新时代牛马3 小时前
内核调试方法
linux·学习
墨痕诉清风4 小时前
Linux系统设置上海时间(24小时制)
linux·运维·服务器
utf8mb4安全女神4 小时前
脚本模块化
linux·运维·服务器