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.
2.2 GCC编译流程:四步走
GCC编译一个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 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命令就不现实了。到时候你就知道,自动化构建工具到底有多香。
可可精品资料库