2. GCC入门

说到C/C++编译,GCC绝对是绕不开的基石。我最早接触GCC是在大学做嵌入式开发的时候,那时候觉得它就是个黑盒子,敲个gcc hello.c就能跑。后来踩了不少坑,才慢慢摸透它的脾气。今天咱们就把它掰开揉碎了聊聊。

2.1 GCC的安装

不同平台安装方式不太一样,我分别说说。

Linux(Ubuntu/Debian)

sudo apt update
sudo apt install build-essential

这条命令会把GCC、G++、make等工具链一次性装好。我个人习惯装完后跑一下gcc --version确认版本。

macOS

xcode-select --install

或者用Homebrew:

brew install gcc

注意macOS自带的clang有时候会被软链接成gcc,其实不是真正的GCC。我建议用gcc-13这样的具体版本号来调用。

Windows

推荐用MinGW-w64。下载安装后记得把bin目录加到环境变量PATH里。嗯,这里要注意:别装错版本,64位系统选x86_64,32位选i686。

小技巧:装完后在终端敲gcc -v,能看到详细的版本信息和配置参数。我经常用这招确认当前用的是哪个编译器。

2.2 基本用法:从源码到可执行文件

先写个最简单的程序:

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

int main() {
    printf("Hello, GCC!\n");
    return 0;
}

编译它:

gcc hello.c -o hello

然后./hello就能看到输出了。就这么简单?其实背后GCC干了四件事:预处理、编译、汇编、链接。我刚开始学的时候觉得没必要了解这些细节,直到有一次遇到链接错误,折腾了两天才明白是库没链接对。

咱们可以分步看看:

gcc -E hello.c -o hello.i    # 预处理
gcc -S hello.i -o hello.s    # 编译成汇编
gcc -c hello.s -o hello.o    # 汇编成目标文件
gcc hello.o -o hello         # 链接

实际开发中很少这么干,但调试复杂问题时会用到。比如想看宏展开结果,就用-E。

2.3 常用编译选项详解

这部分是重点,我按使用频率排序来讲。

-O 优化选项

控制编译器的优化程度。有这几个级别:

选项 含义 编译时间 执行速度
-O0 不优化(默认) 最快 最慢
-O1 基本优化 较快 较快
-O2 更多优化 较慢 很快
-O3 激进优化 最慢 最快
-Os 优化体积 中等 中等

我个人习惯:开发调试用-O0,发布用-O2。-O3有时候会引入一些奇怪的bug,比如循环展开导致的多线程问题。我曾经在一个音频处理项目里用-O3,结果声音出现了爆音,查了两天才发现是优化过度导致时序变了。

注意:优化级别越高,调试越困难。因为编译器会重排指令、内联函数,导致断点位置和源码对不上。所以调试时一定用-O0。

-g 调试信息

生成调试符号表,让GDB能定位到源码行号、变量名等。

gcc -g hello.c -o hello

我建议:只要不是最终发布版本,都加上-g。它不会影响执行速度,只是让可执行文件变大一点。而且就算发布了,也可以保留一份带调试信息的版本,线上出问题能快速定位。

-Wall 警告选项

开启所有常见的警告。注意是「所有常见」的,不是「所有」的。

gcc -Wall hello.c -o hello

还有更严格的:

gcc -Wall -Wextra -Werror hello.c -o hello

-Werror会把所有警告当成错误处理。我刚开始觉得这太苛刻了,直到有一次线上崩溃是因为一个未初始化的变量。从那以后,我的项目都强制开启-Werror。

我的经验:团队协作时,建议把-Wall -Wextra -Werror写进Makefile或CMakeLists.txt里。这样谁提交的代码有警告,编译就直接失败,省得代码审查时扯皮。

其他常用选项

  • -std=c11:指定C语言标准。我一般用-std=c11或-std=c17。
  • -I:添加头文件搜索路径。比如-I./include。
  • -L:添加库文件搜索路径。比如-L./lib。
  • -l:链接指定库。比如-lm链接数学库。
  • -D:定义宏。比如-DDEBUG相当于在代码里写了#define DEBUG。

举个例子,综合使用:

gcc -Wall -Wextra -O2 -g -std=c11 -I./include -L./lib -lm main.c -o app

这条命令的含义是:开启严格警告、二级优化、带调试信息、使用C11标准、指定头文件和库路径、链接数学库。

2.4 知识体系总览

下面这张图是我整理的GCC入门知识结构,方便你对照着学:

GCC入门知识体系 GCC编译器 安装与配置 Linux: apt install macOS: brew install Windows: MinGW-w64 基本用法 预处理 -E 编译 -S 汇编 -c 链接 -o 编译选项 -O0/O1/O2/O3/Os -g 调试信息 -Wall -Wextra -Werror -std -I -L -l -D 实践建议:开发用 -O0 -g -Wall 发布用 -O2 -Wall -Werror

2.5 避坑指南

最后分享几个我踩过的坑:

  • 忘记链接数学库:用了sqrt()、sin()这些函数,但没加-lm。编译不会报错,链接时报一堆未定义引用。我第一次遇到时懵了半天。
  • 头文件路径不对:用了第三方库,但没加-I指定路径。报错fatal error: xxx.h: No such file or directory。检查一下路径是不是写对了。
  • 优化导致调试异常:用-O2调试,断点乱跳,变量值显示不对。改成-O0就好了。
  • 32位和64位混用:在64位系统上编译32位程序,需要加-m32,而且得装32位的库。我有个项目因为这个在客户机器上跑不起来,被骂了一顿。
我的习惯:写个简单的Makefile,把常用选项都配好。每次编译就敲make,省得记一堆参数。后面章节会详细讲Makefile的用法。

好了,GCC入门就聊到这儿。这些选项你多用几次就熟了,关键是理解每个选项背后的含义。下次遇到编译问题,先想想是不是优化级别不对,或者警告没开全。