gcc 命令详解与参数实测(Ubuntu 22.04)
gcc 命令逐参数实测:一步编译与 -o、四阶段管线 -E/-S/-c、-Wall/-Wextra/-Werror 警告矩阵、-O0~-O3 优化 274ms→1ms、-std 标准对照、-I 头文件与 -L/-l 链接顺序、-fPIC -shared 动态库与 rpath、-static 静态链接体积对照、-pthread、-D/-dM 宏、@响应文件与六类报错解读,全部回显在 Ubuntu 22.04(gcc 11.4.0)实测通过。
gcc 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 gcc 的每一个常用参数——一步编译与
-o、四阶段管线-E/-S/-c、多文件工程、警告矩阵-Wall/-Wextra/-Werror/-w、优化-O0~-O3/-Os/-Og、语言标准-std、头文件搜索-I、库链接-L/-l、动态库-fPIC -shared与-Wl,-rpath、静态链接-static、线程-pthread、宏-D/-U/-dM、响应文件@file与典型报错解读,全部命令与回显已在 Ubuntu 22.04(gcc 11.4.0)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64),root 身份执行
- gcc 版本:11.4.0(
gcc --version实测:gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0) - 配套工具链:g++ 11.4.0、GNU Make 4.3、cmake 3.22.1、binutils 2.38、glibc 2.35(ldd 实测)
- 安装来源:
build-essential12.9ubuntu3 元包拉齐 gcc/g++/make 及对应 dev 库 /usr/bin/gcc -> gcc-11为符号链接,/usr/bin/cc -> /etc/alternatives/cc由 alternatives 管理- gcc 默认语言标准:
gnu17(GCC 11 的 C 默认标准,-dM实测__STDC_VERSION__ 201710L)
💡 技术拓展:gcc 是 GNU Compiler Collection 的驱动程序,真正干活的是四个后台程序——
cc1(C 前端,预处理+编译)、as(汇编器,来自 binutils)、collect2/ld(链接器)、cpp(独立预处理器,现已基本由 cc1 内联替代)。gcc 命令只是把这些阶段按参数要求串起来。C++ 编译由 g++ 驱动完成(本专栏姊妹篇),工程化组织则交给 make 与 cmake。
二、命令概览
2.1 语法与四阶段管线
gcc 的基本语法是 gcc [options] [source files] [-l libraries]:选项以 - 开头,源文件按后缀识别语言(.c C、.cpp/.cc/.cxx C++、.s 汇编、.o 目标文件)。一次 gcc 调用内部依次经过四个阶段,每个阶段有对应的"停在这里"参数:
| 阶段 | 输入 → 输出 | 停止参数 | 工具 |
|---|---|---|---|
| 预处理 | .c → .i | -E | cc1(内联 cpp) |
| 编译 | .i → .s | -S | cc1 |
| 汇编 | .s → .o | -c | as |
| 链接 | .o → 可执行 | (默认走完) | collect2 → ld |
2.2 环境速览
$ cat /etc/os-release | head -2
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
$ uname -m
x86_64
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -dumpversion
11
$ gcc -dumpmachine
x86_64-linux-gnu
$ ls -l /usr/bin/gcc /usr/bin/cc
lrwxrwxrwx 1 root root 20 Oct 17 02:00 /usr/bin/cc -> /etc/alternatives/cc
lrwxrwxrwx 1 root root 6 Aug 5 2021 /usr/bin/gcc -> gcc-11
$ dpkg -l | grep -E "^ii +(gcc|build-essential|binutils) "
ii binutils 2.38-4ubuntu2.12 amd64 GNU assembler, linker and binary utilities
ii build-essential 12.9ubuntu3 amd64 Informational list of build-essential packages
ii gcc 4:11.2.0-1ubuntu1 amd64 GNU C compiler
-dumpversion 输出主版本 11,-dumpfullversion 输出 11.4.0;-dumpmachine 输出目标三元组,交叉编译场景用于确认目标架构。
三、基础编译:一步到位与 -o
3.1 默认输出 a.out
$ cat hello.c
#include <stdio.h>
int main(void)
{
printf("hello, gcc!\n");
return 0;
}
$ gcc hello.c # 不带 -o,默认输出 a.out
$ ls -l a.out
-rwxr-xr-x 1 root root 15960 Oct 17 02:14 a.out
$ ./a.out
hello, gcc!
3.2 -o 指定输出名与路径
$ gcc hello.c -o hello
$ ./hello
hello, gcc!
$ gcc hello.c -o /tmp/gccdemo/basic/hello2 # -o 可写绝对路径
$ ls -l /tmp/gccdemo/basic/hello2
-rwxr-xr-x 1 root root 15960 Oct 17 02:14 /tmp/gccdemo/basic/hello2
3.3 -v 观察完整编译管线
-v 把 gcc 驱动的每一步内部调用打印出来,是排查头文件搜索路径、链接库顺序的第一利器:
$ gcc -v hello.c -o hello3
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-1ubuntu1~22.04.3' ...
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04.3)
/usr/lib/gcc/x86_64-linux-gnu/11/cc1 -quiet -v ... -o /tmp/ccEYmjhb.s
GNU C17 (Ubuntu 11.4.0-1ubuntu1~22.04.3) version 11.4.0 (x86_64-linux-gnu)
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/11/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
as -v --64 -o /tmp/cckViodD.o /tmp/ccEYmjhb.s
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:...
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:...
COLLECT_GCC_OPTIONS='-v' '-o' 'hello3' '-mtune=generic' '-march=x86-64'
可确认:默认头文件搜索顺序是 gcc 内部 include → /usr/local/include → /usr/include/x86_64-linux-gnu → /usr/include;链接默认带 -pie -z now -z relro(Ubuntu 启用了默认位置无关可执行与即时绑定加固)。
四、-c 与多文件工程
-c(compile)只编译汇编出 .o,不链接。工程里有多个源文件时,逐个 -c 再统一链接,改哪个文件只需重编哪个:
$ cat util.h
#ifndef UTIL_H
#define UTIL_H
int add(int a, int b);
long fact(int n);
#endif
$ cat util.c
#include "util.h"
int add(int a, int b)
{
return a + b;
}
long fact(int n)
{
long r = 1;
while (n > 1)
r *= n--;
return r;
}
$ cat main.c
#include <stdio.h>
#include "util.h"
int main(void)
{
printf("add(3,4)=%d, fact(5)=%ld\n", add(3, 4), fact(5));
return 0;
}
$ gcc -c util.c # 生成 util.o
$ gcc -c main.c # 生成 main.o
$ gcc -c main.c -o main_obj.o # -c 也能配合 -o 改名
$ ls -l *.o
-rw-r--r-- 1 root root 1360 Oct 17 02:14 main.o
-rw-r--r-- 1 root root 1296 Oct 17 02:14 main_obj.o
-rw-r--r-- 1 root root 1376 Oct 17 02:14 util.o
$ gcc main.o util.o -o app # 链接 .o 成可执行
$ ./app
add(3,4)=5, fact(5)=120
$ gcc main.c util.c -o app_all # 一步到位:源文件全部交给 gcc
$ ./app_all
add(3,4)=5, fact(5)=120
$ gcc -c util.c main.c # -c 一次接多个源文件
$ ls -l *.o
⚠️ 注意:
gcc main.o util.o -o app这类链接多个.o必须一次性给出;若只给gcc main.o -o app,链接器会报undefined reference to 'add'——因为 util.o 未参与链接。
五、警告控制:-Wall / -Wextra / -Werror / -w
用一段带 4 处问题的代码实测警告矩阵:
int neg(int x)
{
if (x < 0)
return -x; /* x>=0 时无返回值 */
}
static int helper(int flags, int mode)
{
return flags; /* mode 未使用 */
}
int main(void)
{
int unused_var = 42;
int overflow = 2147483647 + 1;
printf("%d %d\n", neg(-5), helper(1, 2));
return 0;
}
实测对照(同一份代码、不同警告开关):
$ gcc warn.c -o warn_none
warn.c:17:31: warning: integer overflow in expression of type 'int' results in '-2147483648' [-Woverflow]
不加任何开关也有 1 条 -Woverflow(溢出属于默认开启的高价值警告)。
$ gcc -Wall warn.c -o warn_wall
warn.c:17:31: warning: integer overflow ... [-Woverflow]
warn.c:17:9: warning: unused variable 'overflow' [-Wunused-variable]
warn.c:16:9: warning: unused variable 'unused_var' [-Wunused-variable]
warn.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
-Wall 新增未使用变量与缺返回值;但它并不包含所有警告(名字里的 all 有历史误导性):
$ gcc -Wall -Wextra warn.c -o warn_both
warn.c:9:34: warning: unused parameter 'mode' [-Wunused-parameter]
(-Wextra 补上了未使用的函数参数,共 5 条)
$ gcc -Wall -Werror warn.c -o warn_err
warn.c:17:31: error: integer overflow ... [-Werror=overflow]
...
cc1: all warnings being treated as errors
-Werror 把所有警告升级为错误,产物不会生成,常用于 CI 强制零警告。配合 cat >> Makefile 的约定是 -Wall -Wextra -Werror 三件套。
$ gcc -w warn.c -o warn_w # -w 关闭一切警告,回显干净
| 开关 | 作用 | 实测新增 |
|---|---|---|
| (默认) | 最基本警告 | -Woverflow |
| -Wall | 常用警告大礼包 | -Wunused-variable、-Wreturn-type |
| -Wextra | 补充 -Wall 未含项 | -Wunused-parameter |
| -Werror | 警告升级为错误 | 全部 error:,无产物 |
| -w | 静默所有警告 | 无任何回显 |
六、优化等级:-O0 ~ -O3、-Os、-Og
用一段 2000 万次循环的求和代码实测各档优化对运行时间的影响:
$ gcc -O0 loop.c -o loop_O0 # 不优化(默认即 -O0)
$ gcc -O1 loop.c -o loop_O1
$ gcc -O2 loop.c -o loop_O2 # 发布常用档
$ gcc -O3 loop.c -o loop_O3 # 激进优化+向量化
$ gcc -Os loop.c -o loop_Os # 按 -O2 收缩体积
$ gcc -Og loop.c -o loop_Og # 优化但保持可调试
$ ls -l loop_O0 loop_O1 loop_O2 loop_O3 loop_Os loop_Og
-rwxr-xr-x 1 root root 15960 Oct 17 02:14 loop_O0
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_O1
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_O2
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_O3
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_Og
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_Os
$ time ./loop_O0 >/dev/null
real 0m0.274s
$ time ./loop_O1 >/dev/null
real 0m0.039s
$ time ./loop_O2 >/dev/null
real 0m0.001s
-O0 到 -O2 运行时间从 274ms 降到 1ms(-O2 起对纯计算循环做了强度削减/部分求值)。体积差异不大,因为小代码优化主要改指令而非膨胀。
调试信息 -g 与瘦身 -s:
$ gcc -O0 -g loop.c -o loop_O0g
$ gcc -O2 -g loop.c -o loop_O2g
$ ls -l loop_O0 loop_O0g loop_O2 loop_O2g
-rwxr-xr-x 1 root root 15960 Oct 17 02:14 loop_O0
-rwxr-xr-x 1 root root 17232 Oct 17 02:14 loop_O0g # -g 增加 DWARF 调试段
-rwxr-xr-x 1 root root 15968 Oct 17 02:14 loop_O2
-rwxr-xr-x 1 root root 17784 Oct 17 02:14 loop_O2g
💡 经验法则:开发期
-Og -g(可断点、变量不丢),发布期-O2(或-O3上限再压测),嵌入/发布带-s也可在链接后由strip完成。-march=native让 gcc 按 CPU 支持的指令集生成代码,但产物不可跨机型迁移。
七、语言标准:-std=c89/c99/c11/c17 与 gnu 变体
$ cat modern.c
#include <stdio.h>
int main(void)
{
int total = 0;
for (int i = 1; i <= 10; i++) /* C99 起允许在 for 里声明变量 */
total += i;
printf("total=%d\n", total);
return 0;
}
$ gcc -std=c89 modern.c -o m89
modern.c:6:5: error: 'for' loop initial declarations are only allowed in C99 or C11 mode
6 | for (int i = 1; i <= 10; i++)
| ^~~
modern.c:6:5: note: use option '-std=c99', '-std=gnu99', '-std=c11' or '-std=gnu11' to compile your code
$ gcc -std=c99 modern.c -o m99 && ./m99
total=55
$ gcc -std=c11 modern.c -o m11
$ gcc -std=c17 modern.c -o m17
-std=gnu89/gnu99/... 在对应 ISO 标准上叠加 GNU 扩展(默认是 gnu17)。实测宏 __STDC_VERSION__ 可直接核对当前生效标准:
$ gcc -dM -E - </dev/null | grep -E "__STDC_VERSION__|__GNUC__ |__GNUC_MINOR__"
#define __GNUC__ 11
#define __STDC_VERSION__ 201710L
#define __GNUC_MINOR__ 4
$ gcc -std=c11 -dM -E - </dev/null | grep __STDC_VERSION__
#define __STDC_VERSION__ 201112L
| 标准参数 | 含义 | STDC_VERSION 实测 |
|---|---|---|
| -std=c89 / -std=c90 | ANSI C | (未定义该宏) |
| -std=c99 | ISO C99 | 199901L |
| -std=c11 | ISO C11 | 201112L |
| -std=c17 | ISO C17(gcc 11 默认是 gnu17,即 201710L) | 201710L |
| -std=gnuNN | 对应标准 + GNU 扩展 | 同 ISO 值 |
八、头文件搜索路径:-I
$ tree -L2 . 2>/dev/null || find . -type f | sort
./include/mymath.h
./src/main.c
./mymath.c
$ cat src/main.c
#include <stdio.h>
#include <mymath.h>
...
不加 -I 时找不到自定义目录里的尖括号头文件:
$ gcc src/main.c -o app
src/main.c:2:10: fatal error: mymath.h: No such file or directory
2 | #include <mymath.h>
| ^~~~~~~~~~
compilation terminated.
$ gcc -Iinclude src/main.c -o app
/usr/bin/ld: /tmp/ccKoEegX.o: in function `main':
main.c:(.text+0x15): undefined reference to `circle_area'
collect2: error: ld returned 1 exit status
-Iinclude 解决了头文件查找,但链接阶段还缺实现文件——把 mymath.c 一并加入即可:
$ gcc -Iinclude src/main.c mymath.c -o app_full
$ ./app_full
area=12.5664
用 -v 确认 -I 目录插到了搜索链最前面:
$ gcc -Iinclude -v src/main.c -o app 2>&1 | sed -n "/#include <...>/,/End of search/p"
#include <...> search starts here:
include
/usr/lib/gcc/x86_64-linux-gnu/11/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
💡
-iquote只对#include "..."生效;-isystem添加的目录被标记为系统头文件目录,其中的警告会被抑制。第三方库开发里,"找不到头文件" 与 "undefined reference" 是两类独立问题:前者在编译期、靠-I解决;后者在链接期、靠-L/-l或把源文件一起编译解决。
九、库链接:静态库 ar 与 -L / -l
$ cat mymath.h
double circle_area(double r);
$ cat mymath.c
#include "mymath.h"
double circle_area(double r)
{
return 3.141592653589793 * r * r;
}
$ gcc -c mymath.c
$ ar rcs libmylib.a mymath.o # r 替换/插入、c 创建、s 建索引
$ ls -l libmylib.a mymath.o
-rw-r--r-- 1 root root 1800 Oct 17 02:14 libmylib.a
-rw-r--r--r-- 1 root root 1344 Oct 17 02:14 mymath.o
$ cat main.c
#include <stdio.h>
#include "mymath.h"
int main(void)
{
printf("area=%.4f\n", circle_area(2.0));
return 0;
}
$ gcc main.c -lmylib -o app_noflag # 不给 -L,默认路径找不到 libmylib.a
/usr/bin/ld: cannot find -lmylib
collect2: error: ld returned 1 exit status
$ gcc main.c -L. -lmylib -o app # -L. 把当前目录加入库搜索路径
$ ./app
area=12.5664
链接顺序有讲究——库要放在引用它的目标文件之后:
$ gcc -L. -lmylib main.c -o app_bad # 库在源文件之前
/usr/bin/ld: main.c:(.text+0x15): undefined reference to `circle_area'
ld 从左到右扫描,遇到 -lmylib 时 main.o 还没出现、无人引用该符号,库就被丢弃了。规则:被依赖的库放后面。
-l 的命名解析规则:-lmylib 依次找动态库 libmylib.so 与静态库 libmylib.a(动态优先)。
十、动态库:-fPIC、-shared、LD_LIBRARY_PATH 与 -Wl,-rpath
$ gcc -fPIC -shared mymath.c -o libmymath.so # 位置无关代码 + 共享对象
$ ls -l libmymath.so
-rwxr-xr-x 1 root root 15216 Oct 17 02:14 libmymath.so
$ gcc main.c -L. -lmymath -o app
$ ./app
./app: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
$ ldd app
linux-vdso.so.1 (0x00007ffcae49d000)
libmymath.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007d927af9f000)
链接期记录了"需要 libmymath.so",运行期 loader 却找不到它——-L 只影响链接期,不影响运行期搜索。两种解法:
# 解法一:LD_LIBRARY_PATH 环境变量(临时)
$ LD_LIBRARY_PATH=/tmp/gccdemo/shlib ./app
area=12.5664
# 解法二:编译期烧写 rpath(永久)
$ gcc main.c -L. -lmymath -Wl,-rpath=/tmp/gccdemo/shlib -o app_rpath
$ ./app_rpath
area=12.5664
$ ldd app_rpath
libmymath.so => /tmp/gccdemo/shlib/libmymath.so (0x000071e6e45c7000)
$ readelf -d app | grep -E "RPATH|RUNPATH|NEEDED"
0x0000000000000001 (NEEDED) Shared library: [libmymath.so]
-Wl,<opt> 把选项透传给链接器 ld(这里是 -rpath=...);生产部署更规范的做法是把 .so 装入 /usr/local/lib 并执行 ldconfig 刷新缓存。
十一、-static 静态链接
$ gcc hello.c -o hello_dyn
$ gcc -static hello.c -o hello_static
$ ls -l hello_dyn hello_static
-rwxr-xr-x 1 root root 15960 Oct 17 02:14 hello_dyn
-rwxr-xr-x 1 root root 900368 Oct 17 02:14 hello_static # 体积 ×57
$ ./hello_static
hello, static link!
$ ldd hello_dyn
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000761d75fc5000)
$ ldd hello_static
not a dynamic executable
-static 把 libc 等全部静态打入,产物不依赖目标机的动态库版本(适合部署到老旧或最小化系统),代价是体积暴涨且失去库的安全更新能力。
十二、-pthread 线程支持
$ cat thread.c
#include <pthread.h>
#include <stdio.h>
static void *work(void *arg)
{
printf("thread %d running\n", *(int *)arg);
return NULL;
}
int main(void)
{
pthread_t t1, t2;
int a = 1, b = 2;
pthread_create(&t1, NULL, work, &a);
pthread_create(&t2, NULL, work, &b);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
puts("all threads done");
return 0;
}
$ gcc thread.c -pthread -o t_pthread
$ ./t_pthread
thread 1 running
thread 2 running
all threads done
$ ls -l /usr/lib/x86_64-linux-gnu/libpthread*
-rw-r--r-- 1 root root 8 Sep 3 14:20 /usr/lib/x86_64-linux-gnu/libpthread.a
-rw-r--r-- 1 root root 21448 Sep 3 14:20 /usr/lib/x86_64-linux-gnu/libpthread.so.0
$ ldd t_pthread
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000765710fb4000)
glibc 2.34 起 pthread 已并入 libc(ldd 不再显示 libpthread.so.1),但 -pthread 仍需保留:它同时设置编译期宏与链接期依赖,保证在老 glibc 与其他平台上的可移植性。
十三、宏定义:-D / -U / -dM
$ cat version.c
#include <stdio.h>
int main(void)
{
#if VER >= 3
puts("new path");
#else
puts("legacy path");
#endif
#ifdef DEBUG
puts("[debug] build with DEBUG");
#endif
return 0;
}
$ gcc -DVER=1 version.c -o v1 && ./v1
ver=1 (legacy path)
$ gcc -DVER=1 -DDEBUG version.c -o v2 && ./v2
[debug] build with DEBUG
$ gcc -DVER=3 -DDEBUG version.c -o v3 && ./v3
[debug] build with DEBUG
ver=3 (new path)
$ gcc -DAPP_NAME=\"build-server\" msg.c -o msg && ./msg # 字符串宏:内嵌引号
app=build-server
-DVER=1 等价于源码顶部写 #define VER 1;带空格/字符串的值需在 shell 里包好引号;-UDEBUG 可取消命令行或源码前面的同名定义。
预定义宏用 -dM -E 全量导出(gcc 11 在 x86_64 Linux 上共 367 个):
$ gcc -dM -E - </dev/null | grep -E "__linux|__x86_64|__SIZEOF_LONG__"
#define __linux 1
#define __unix 1
#define __SIZEOF_LONG__ 8
#define __x86_64 1
$ gcc -dM -E - </dev/null | grep -c "#define"
367
十四、编译阶段拆分:-E / -S / -c / -save-temps
$ cat sum.c
#include <stdio.h>
#define N 5
int sum_to(int n)
{
int s = 0;
for (int i = 1; i <= n; i++)
s += i;
return s;
}
int main(void)
{
printf("sum1..%d=%d\n", N, sum_to(N));
return 0;
}
# 阶段一:-E 只做预处理,输出展开后的纯 C
$ gcc -E sum.c -o sum.i
$ ls -l sum.i
-rw-r--r-- 1 root root 18076 Oct 17 02:14 sum.i
$ tail -14 sum.i
# 5 "sum.c"
int sum_to(int n)
{
int s = 0;
for (int i = 1; i <= n; i++)
s += i;
return s;
}
int main(void)
{
printf("sum1..%d=%d\n", 5, sum_to(5)); /* N 已替换为 5 */
return 0;
}
$ grep -c "extern" sum.i
93 /* 头文件 stdio.h 全量展开,可见 -E 产物之大 */
# 阶段二:-S 生成汇编
$ gcc -S sum.c -o sum.s
$ head -20 sum.s
.file "sum.c"
.text
.globl sum_to
.type sum_to, @function
sum_to:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
movq %rsp, %rbp
movl %edi, -20(%rbp)
movl $0, -8(%rbp)
movl $1, -4(%rbp)
...
$ gcc -S -O2 sum.c -o sum_O2.s # -O2 后循环被改写,main 直接内联常量 15
$ grep -A2 "movl.*\$15" sum_O2.s | head -3
movl $15, %ecx
# 阶段三:-c 生成目标文件
$ gcc -c sum.c -o sum.o && file sum.o
sum.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
-save-temps 一步保留全部中间产物:
$ gcc -save-temps sum.c -o sum
$ ls sum.*
sum sum.i sum.o sum.s
十五、响应文件 @file
参数过长(大型工程几千个源文件)会撞上 ARG_MAX,gcc 支持从文件读取参数:
$ cat release.rsp
-Wall
-Wextra
-O2
-o app_release
$ gcc @release.rsp main.c util.c
$ ./app_release
add(2,3)=5
$ gcc @release.rsp -o app_single main.c # rsp 里 -o 与命令行可混用
/usr/bin/ld: /tmp/ccgYLZUL.o: in function `main':
main.c:(.text.startup+0x13): undefined reference to `add'
collect2: error: ld returned 1 exit status
@文件 内容按空白分词后与命令行参数合并,顺序敏感——缺 util.c 就再现 undefined reference。
十六、错误场景与诊断解读
$ cat syntax.c
#include <stdio.h>
int main(void)
{
printf("oops\n") /* 漏分号 */
return 0;
}
$ gcc syntax.c -o syntax
syntax.c:5:21: error: expected ';' before 'return'
5 | printf("oops\n")
| ^
| ;
$ cat undecl.c
int main(void)
{
x = 3;
return 0;
}
$ gcc undecl.c -o undecl
undecl.c:3:5: error: 'x' undeclared (first use in this function)
3 | x = 3;
| ^
$ cat linkerr.c
extern void missing_fn(int);
int main(void)
{
missing_fn(1);
return 0;
}
$ gcc linkerr.c -o linkerr
/usr/bin/ld: /tmp/ccswl83u.o: in function `main':
linkerr.c:(.text+0x9): undefined reference to `missing_fn'
collect2: error: ld returned 1 exit status
$ gcc nofile.c -o nofile
cc1: fatal error: nofile.c: No such file or directory
$ gcc
gcc: fatal error: no input files
compilation terminated.
诊断解读速记:
| 报错关键行 | 阶段 | 根因方向 |
|---|---|---|
error: expected ';' ... / undeclared | cc1(编译期) | 源码语法/拼写/缺头文件 |
fatal error: xxx.h: No such file or directory | 预处理期 | 缺 -I 或缺 dev 包 |
undefined reference to ... | ld(链接期) | 缺 .o/库、-L/-l 顺序错 |
cannot find -lxxx | ld(链接期) | -L 路径不对或库文件名不符 |
error while loading shared libraries | 运行期 | rpath/LD_LIBRARY_PATH/ldconfig 缺失 |
cc1: all warnings being treated as errors | cc1 | -Werror 拦截 |
十七、参数速查表
| 参数 | 作用 | 实测要点 |
|---|---|---|
-o <file> | 指定输出文件名 | 默认 a.out;可带绝对路径 |
-c | 只编译不链接 | 产出 .o;多文件最后统一链接 |
-E | 只做预处理 | 输出展开后的 .i |
-S | 只编译到汇编 | 输出 .s;-O2 会明显改写 |
-v | 打印驱动过程 | 含头文件搜索路径与链接命令 |
-Wall / -Wextra | 常用/补充警告 | extra 补 unused-parameter |
-Werror | 警告即错误 | CI 零警告标配 |
-w | 关闭全部警告 | 回显干净 |
-O0/-O1/-O2/-O3/-Os/-Og | 优化档 | 实测 274ms→1ms(O0→O2) |
-g | 生成调试信息 | 体积 +1~2KB(小工程) |
-std=c99/c11/c17/gnu17 | 语言标准 | 默认 gnu17 |
-I<dir> | 头文件搜索目录 | 插入搜索链最前 |
-L<dir> | 库搜索目录 | 仅链接期有效 |
-l<name> | 链接 libname.so/.a | 顺序在被依赖者之后 |
-fPIC | 位置无关代码 | 动态库必需 |
-shared | 生成 .so | 与 -fPIC 搭配 |
-Wl,<opt> | 透传链接器选项 | 如 -Wl,-rpath=... |
-static | 全静态链接 | 体积 ×57,ldd 显示 not a dynamic executable |
-pthread | 线程编译链接约定 | glibc≥2.34 仍建议保留 |
-D<name>[=val] | 定义宏 | 字符串值需内嵌引号 |
-U<name> | 取消宏定义 | 对抗 -D 或源码 define |
-dM -E | 导出全部预定义宏 | 本环境 367 个 |
-dumpversion/-dumpmachine | 查版本/目标三元组 | 11 / x86_64-linux-gnu |
@file | 从文件读参数 | 应对超长命令行 |
关联阅读:g++(C++ 编译驱动)、make(增量构建编排)、cmake(跨平台构建系统生成器)见本专栏姊妹篇。