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)实测通过。

Linux命令gccLinux系统UbuntuUbuntu 22.04命令行2026/10/17

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-essential 12.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-Ecc1(内联 cpp)
编译.i → .s-Scc1
汇编.s → .o-cas
链接.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=c90ANSI C(未定义该宏)
-std=c99ISO C99199901L
-std=c11ISO C11201112L
-std=c17ISO 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 ';' ... / undeclaredcc1(编译期)源码语法/拼写/缺头文件
fatal error: xxx.h: No such file or directory预处理期缺 -I 或缺 dev 包
undefined reference to ...ld(链接期)缺 .o/库、-L/-l 顺序错
cannot find -lxxxld(链接期)-L 路径不对或库文件名不符
error while loading shared libraries运行期rpath/LD_LIBRARY_PATH/ldconfig 缺失
cc1: all warnings being treated as errorscc1-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(跨平台构建系统生成器)见本专栏姊妹篇。