2. GCC编译器入门

说实话,很多初学者一听到「编译器」三个字就觉得头大。我当年刚入行时也是这样,总觉得这东西离我很远。直到第一次在Linux环境下编译一个C项目,被各种报错搞得焦头烂额,才意识到——不懂GCC,你连代码怎么跑起来的都不知道。

这一章,我们就来把GCC这层窗户纸捅破。我会从安装开始,带你走一遍完整的编译流程,再聊聊那些最常用的编译选项。嗯,都是我在项目里踩过坑之后才真正理解的东西。

2.1 GCC的安装与版本检查

GCC(GNU Compiler Collection)是Linux下最主流的编译器。说白了,它就是把你写的C/C++代码翻译成机器能执行的二进制文件。

安装GCC

不同系统安装方式不一样。我个人习惯用Ubuntu,所以先说说这个:

# Ubuntu/Debian
sudo apt update
sudo apt install build-essential

# CentOS/RHEL
sudo yum groupinstall "Development Tools"

# macOS(通过Homebrew)
brew install gcc

安装完成后,检查一下版本:

gcc --version

你会看到类似这样的输出:

gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
小提示: 我建议你至少用GCC 8.0以上的版本。太老的版本对C11/C17标准的支持不够好,我在一个老项目里就吃过这个亏——代码本地编译没问题,放到服务器上就报错,最后发现是GCC版本太旧。

2.2 GCC编译流程:四步走

GCC编译一个C程序,其实不是一步到位的。它背后经历了四个阶段:预处理、编译、汇编、链接。你想想看,这就像做一道菜——洗菜、切菜、炒菜、装盘,每一步都有它的意义。

下面这张图能帮你理清整个流程:

GCC编译四阶段流程图 源文件 hello.c 预处理 预处理 hello.i 编译 编译 hello.s 汇编 汇编 hello.o 链接 可执行文件 hello #include <stdio.h> 宏展开、头文件包含 C代码 → 汇编代码 语法检查、优化 汇编代码 → 机器码 生成目标文件 目标文件 + 库文件 生成最终可执行文件

第一步:预处理(Preprocessing)

预处理阶段,GCC会处理所有以#开头的指令。比如#include会把头文件内容复制进来,#define会做宏替换。

# 只进行预处理,生成 .i 文件
gcc -E hello.c -o hello.i

你可以打开hello.i看看,里面已经把stdio.h的内容全部展开了。我第一次看到这个文件时吓了一跳——一个简单的printf背后居然有几百行代码。

第二步:编译(Compilation)

这一步才是真正的「编译」。GCC会把预处理后的C代码转换成汇编代码。同时,它会做语法检查和初步优化。

# 编译成汇编代码,生成 .s 文件
gcc -S hello.i -o hello.s
注意: 如果代码有语法错误,比如少了个分号,GCC会在这一步报错。我见过太多新手直接跳过这一步,用gcc hello.c一把梭,结果报错信息看不懂。其实你拆开来看,问题就清晰多了。

第三步:汇编(Assembly)

汇编器会把汇编代码转换成机器码,生成目标文件(.o文件)。这个文件已经是二进制了,但还不能直接运行——因为它还没有链接库函数。

# 汇编成目标文件,生成 .o 文件
gcc -c hello.s -o hello.o

第四步:链接(Linking)

链接器把目标文件和需要的库文件(比如libc)合并在一起,生成最终的可执行文件。这一步解决了函数调用的地址问题。

# 链接生成可执行文件
gcc hello.o -o hello

当然,你也可以一步到位:

gcc hello.c -o hello

但说实话,我建议初学者至少手动拆开跑一遍。为什么?因为当你遇到链接错误时,比如undefined reference to 'xxx',你就知道问题出在链接阶段,而不是编译阶段。排查起来快得多。

2.3 常用编译选项

GCC的编译选项非常多,但日常开发中常用的就那么几个。我挑最核心的四个来讲。

选项 作用 使用场景
-o 指定输出文件名 所有场景
-g 生成调试信息 调试阶段
-Wall 开启所有常见警告 开发阶段
-O2 二级优化 发布阶段

-o:指定输出文件名

这个最简单。不加-o的话,GCC默认生成a.out。我见过有人项目里一堆a.out,分不清哪个是哪个。所以,养成好习惯:

gcc hello.c -o hello

-g:生成调试信息

这个选项会在可执行文件中加入调试符号表。有了它,你才能用GDB进行断点调试、查看变量值。

gcc -g hello.c -o hello
警告: 加了-g后,生成的文件会变大。我曾经在一个嵌入式项目里忘了去掉-g,结果固件大小超了Flash容量,排查了半天才发现是调试信息占的空间。所以发布版本一定要去掉-g。

-Wall:开启所有常见警告

这个选项我强烈建议你一直开着。它不会影响代码运行,但能帮你发现很多潜在问题。

gcc -Wall hello.c -o hello

比如你写了个未使用的变量,或者if语句里用了赋值号=而不是等号==,-Wall都会提醒你。我刚开始写C时,总觉得警告无所谓,直到有一次因为=和==搞混,线上服务挂了半小时……从那以后,我再也不敢忽略警告了。

-O2:二级优化

优化选项有-O0(不优化)、-O1、-O2、-O3(最高优化)。-O2是推荐的生产环境选项,它在编译时间和运行效率之间取得了很好的平衡。

gcc -O2 hello.c -o hello
经验之谈: 调试时用-O0,发布时用-O2。为什么?因为优化后的代码可能会改变执行顺序,导致你在GDB里单步调试时,看到代码跳来跳去,根本对不上源码行号。我踩过这个坑,调试一个-O3优化的程序,断点根本打不住,最后只能关掉优化重新编译。

2.4 实战:一个完整的编译示例

我们来写一个简单的程序,把上面讲的选项都用上。

// hello.c
#include <stdio.h>

#define GREETING "Hello, GCC!"

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

分步编译:

# 1. 预处理
gcc -E hello.c -o hello.i

# 2. 编译(带调试信息和警告)
gcc -S -g -Wall hello.i -o hello.s

# 3. 汇编
gcc -c hello.s -o hello.o

# 4. 链接(带二级优化)
gcc -O2 hello.o -o hello

# 运行
./hello

输出结果:

Hello, GCC!

你看,整个流程走下来,每一步都清清楚楚。以后遇到编译问题,你就知道该去哪个阶段找原因了。

好了,这一章的内容就到这里。GCC的安装、编译四阶段、常用选项,这些是嵌入式开发的基本功。下一章我们会聊Makefile——当你的项目有几十个源文件时,手动敲gcc命令就不现实了。到时候你就知道,自动化构建工具到底有多香。


可可精品资料库