-c
和 -o
是 GCC 编译器最常用的两个选项,下面是它们的具体含义和配合使用说明:
-c
:只编译,不链接.c
或 .cpp
文件编译为目标文件 .o
作用:用于构建大型项目的中间目标文件,适合配合 Makefile
。
-o <输出文件名>
:指定输出文件名.o
文件,也可以用于最终的可执行文件gcc -c main.c -o main.o
含义:
main.c
编译为 main.o
main()
是否完整、不会报 _start
缺失)# 编译每个源文件为目标文件
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
# 链接成最终可执行程序
gcc foo.o bar.o -o my_program
选项 | 作用 | 示例 |
---|---|---|
-c |
编译为 .o ,不进行链接 |
gcc -c main.c |
-o |
指定输出文件名(可用于 .o 或可执行) |
gcc -c main.c -o main.o |
情况 | 说明 |
---|---|
忘记加 -c 但用了 -o xxx.o |
编译器会尝试链接,报错找不到 main() 或 _start() |
没加 -o |
默认输出为 a.out (可执行)或 main.o (目标文件) |
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c main.c -o main.o
这里的意思是:
main.c
生成 main.o
,不进行链接如果你在用
Makefile
,或者想把多个.c
编译成.o
再链接为.elf
,需要 -c - o
-I
是 GCC 中用于指定 头文件(header file)搜索路径 的编译选项,是 C/C++ 项目构建中必不可少的一部分。
-I<路径>
或者加空格也可以(不推荐):
-I <路径>
告诉编译器:当遇到 #include
指令时,要去指定的路径找头文件。
适用于:
include/
、inc/
等目录)gcc -I./include -I../drivers -c main.c -o main.o
遇到这段代码时:
#include "my_driver.h"
编译器将按顺序查找:
./include/my_driver.h
../drivers/my_driver.h
/usr/include
)#include
两种写法的区别:写法 | 搜索顺序 |
---|---|
#include "file.h" |
当前源文件目录 → -I 指定路径 → 系统默认路径 |
#include |
-I 指定路径 → 系统默认路径(不查当前目录) |
所以,你用双引号 "
时更适合项目内文件,尖括号 < >
适合标准库或第三方库。
project/
├── include/
│ └── foo.h
├── src/
│ └── foo.c
编译命令:
gcc -I./include -c src/foo.c -o foo.o
-I./FreeRTOS/Source/include
-I./FreeRTOS/Source/portable/GCC/ARM_CM4F
问题 | 建议 |
---|---|
多个 -I 路径时有顺序依赖 |
编译器按顺序查找,建议主项目路径在最前面 |
不要滥用系统路径 /usr/include |
使用 -I 明确引用自定义库更安全 |
多层目录建议用绝对或相对路径 | 避免头文件名冲突 |
选项 | 作用 | 示例 |
---|---|---|
-I |
添加头文件搜索路径 | -I./include -I../lib |
配套使用 | 和 -c 一起用于编译 .c 文件 |
gcc -c -I./include main.c -o main.o |
-mcpu=cortex-m3
和 -mthumb
是 GCC 针对 ARM 架构(特别是嵌入式开发) 的编译选项,用于控制生成的机器指令和目标架构。
下面是详细解释:
-mcpu=cortex-m3
指定目标 CPU 类型为 ARM Cortex-M3。
编译器将:
Cortex-M3 是一个典型的低功耗、低成本嵌入式处理器,广泛用于微控制器(如 STM32F1 系列)。
-mthumb
强制编译器生成 Thumb 指令集 而不是传统的 ARM 指令集。
Thumb 是 ARM 指令集的子集,使用 16 位压缩指令(后来也支持 32 位扩展指令)。
与标准 32 位 ARM 指令相比:
-mthumb
,否则链接时会出错(因为生成的是 ARM 指令,而硬件不支持)。arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -O2 -Wall -c main.c -o main.o
说明:
问题 | 说明 |
---|---|
必须配合 -mthumb |
Cortex-M3 不支持 ARM 指令,忘记加会出错或导致运行异常 |
链接时也要指定 CPU 和模式 | 链接器也需要同样的选项,比如 -mcpu=cortex-m3 -mthumb |
不同 Cortex-M 系列指令支持不同 | 例如:Cortex-M4 支持 DSP/FPU,可用 -mcpu=cortex-m4 -mfpu=... -mfloat-abi=... |
编译选项 | 含义 |
---|---|
-mcpu=cortex-m3 |
目标 CPU 为 ARM Cortex-M3,使用其特性 |
-mthumb |
生成 Thumb 指令(适用于 Cortex-M 系列) |
这是嵌入式 ARM Cortex-M3 开发的基本配置,必不可少。
如果用的是 Cortex-M4 或 M7,可能还要加
-mfpu=...
和-mfloat-abi=...
这两个参数是 针对 ARM Cortex-M 系列中的浮点单元(FPU)配置,特别适用于如 Cortex-M4F、Cortex-M7F 等带硬件浮点支持的内核。
-mfpu=...
:指定使用哪种 FPU(Floating Point Unit)告诉编译器使用哪种类型的浮点单元指令集,以便能正确生成指令。
选项 | 说明 | 适用芯片(举例) |
---|---|---|
-mfpu=fpv4-sp-d16 |
单精度浮点,16 个寄存器 | Cortex-M4F, Cortex-M7F |
-mfpu=fpv5-sp-d16 |
单精度浮点,支持更多指令 | Cortex-M7F(高性能型) |
-mfpu=none |
不使用浮点指令(默认) | Cortex-M0/M3/M4(无FPU) |
注意:M4 和 M7 有带
F
后缀和不带的版本,只有带F
的才有 FPU。
-mfloat-abi=...
:指定浮点参数传递方式(ABI)控制浮点运算中,浮点数据是使用 FPU 寄存器处理,还是用整数寄存器(或内存)来传递参数/返回值。
选项 | 说明 |
---|---|
soft |
所有浮点运算都用软件实现,不用 FPU 指令 |
softfp |
浮点运算用硬件 FPU,但 ABI 与 soft 兼容,浮点传参用整数寄存器 |
hard |
浮点运算用硬件 FPU,浮点参数和返回值通过 FPU 寄存器传递 |
hard
与 soft
/ softfp
不能混合链接。
所以你必须确保:
.a
, .o
)用的是相同的 -mfloat-abi
选项!-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
CPU 是否带 FPU | -mfpu |
-mfloat-abi |
是否使用硬件浮点 | ABI 兼容性 |
---|---|---|---|---|
❌ Cortex-M3 | 不加或 none |
soft |
❌ | 与纯整数 ABI 相同 |
✅ Cortex-M4F | fpv4-sp-d16 |
softfp |
✅ 用硬件计算 | ✅ 与软浮点兼容 |
✅ Cortex-M4F | fpv4-sp-d16 |
hard |
✅ 用硬件计算 | ⚠️ ABI 不兼容软浮点 |
目标芯片 | 推荐选项 |
---|---|
Cortex-M3 | -mcpu=cortex-m3 -mthumb |
Cortex-M4 (无FPU) | -mcpu=cortex-m4 -mthumb -mfpu=none -mfloat-abi=soft |
Cortex-M4F | -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard |
Cortex-M7F | -mcpu=cortex-m7 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard |
-ffreestanding
是 GCC 的一个非常重要的编译选项,尤其在嵌入式开发、裸机编程、内核开发中常用。
-ffreestanding
:开启“自由环境”编译模式(freestanding)告诉编译器:你的程序不运行在标准 C 运行时环境中,而是在一个最小或自定义的运行环境中,比如裸机(bare-metal)或 bootloader。
-fhosted
的区别模式 | 选项 | 编译器假设 | 适用场景 |
---|---|---|---|
hosted 模式 | -fhosted (默认) |
有完整操作系统和标准库(如 libc) | 桌面、Linux 应用开发 |
freestanding 模式 | -ffreestanding |
无标准库、无操作系统支持 | 裸机、嵌入式、内核、RTOS |
-ffreestanding
下的行为不强制要求定义 main()
_start
, Reset_Handler
, boot_entry
等)不假设标准头文件存在
,
等可能无法使用,除非你自己提供它们。不会自动链接 libc
crt0.o
)exit()
结尾arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -ffreestanding -nostdlib -c main.c -o main.o
解释:
-ffreestanding
:告诉编译器我不需要标准库环境-nostdlib
:不链接系统默认标准库(你可能用的是 newlib-nano
或自己的最小 libc)选项 | 含义 |
---|---|
-nostdlib |
不链接标准 C 库和启动代码 |
-nostartfiles |
不链接标准启动文件(如 crt0.o ) |
-nodefaultlibs |
不链接默认的 libgcc、libc 等 |
-Wl,-T |
指定自定义链接脚本(用于控制内存布局等) |
crt0.o
编译器选项 | 说明 |
---|---|
-ffreestanding |
禁用标准 C 运行时假设,适合裸机/操作系统等底层开发 |
必须手动配置 | 启动代码、入口函数、链接脚本、异常向量表等均要手动指定 |
-O0
、-O2
、-Os
是 GCC 的 编译优化等级选项,用来控制编译器如何生成代码,分别代表不同的优化目标。
-O0
(无优化)功能:完全关闭优化(默认)。
特点:
适用场景:调试阶段、开发初期
-O2
(速度优化)功能:启用大多数优化选项,不影响编译时间太多。
优化目标:提高程序运行速度
常见优化:
适用场景:通用程序开发、对性能有要求的应用
-Os
(空间优化)-O2
类似的优化,但会禁用会让代码变大的优化(如循环展开)。优化等级 | 优化目标 | 编译速度 | 程序运行速度 | 可执行文件大小 | 调试友好 | 常见用途 |
---|---|---|---|---|---|---|
-O0 |
无优化 | 快 | 慢 | 大 | ✅ 最友好 | 调试、开发初期 |
-O2 |
提高速度 | 中等 | ✅ 快 | 中 | 一般 | 性能敏感应用 |
-Os |
减小体积 | 中等 | 快(略慢于 -O2 ) |
✅ 小 | 一般 | 嵌入式、ROM 限制环境 |
使用目标 | 推荐编译选项 |
---|---|
调试程序 | -O0 -g |
发布性能优先 | -O2 -march=native (可再加 -flto ) |
发布体积优先(嵌入式) | -Os -ffunction-sections -fdata-sections -Wl,--gc-sections |
-O2
, -Os
)可能影响调试体验(如变量消失、断点跳转不准确)。加 -g
可以保留调试符号。--gc-sections
是 GNU 链接器 (ld
) 的一个选项,用于 去除未使用的节(section),从而 减小最终可执行文件或固件的大小。它在嵌入式开发中非常常用,尤其是资源受限的系统。
--gc-sections
--gc-sections
(Garbage Collection of Sections)告诉链接器:
“将没有被引用的代码段和数据段清理掉(移除)”。
这个机制依赖于:
-ffunction-sections
和 -fdata-sections
选项-ffunction-sections
:将每个函数放入独立的 .text.
段中。-fdata-sections
:将每个全局变量放入独立的 .data.
或 .bss.
段中。这样可以让链接器更精细地控制每个函数/变量的去留。
--gc-sections
_start
或 main
)开始遍历引用关系。gcc -ffunction-sections -fdata-sections -c foo.c
gcc -ffunction-sections -fdata-sections -c bar.c
gcc foo.o bar.o -Wl,--gc-sections -o myapp
-Wl,
是告诉 GCC 把后面的选项传给链接器 ld
。
✅ 必须配合 -ffunction-sections -fdata-sections
才有效
否则多个函数/数据仍在一个段里,链接器无法单独识别每个函数的可达性。
⚠️ 静态初始化或特殊节(如中断向量、构造函数)可能被误删
__attribute__((used))
或 KEEP()
在链接脚本中保护这些符号。示例:
KEEP(*(.isr_vector))
KEEP(*(.init_array))
⚠️ 链接脚本要支持节级保留或精细控制
*(.text)
而不是 KEEP(*(.text.funcname))
,可能导致重要代码被清除。❗ 某些语言特性或库依赖反射/动态行为,不能轻易剔除
--gc-sections
导致的错误?链接失败或运行时出现异常行为,怀疑是符号被移除?
可临时禁用 --gc-sections
或加 __attribute__((used))
进行验证。
使用 objdump -t
、nm
等工具查看符号是否还在。
加 -Wl,--print-gc-sections
可打印出被移除的节:
gcc -Wl,--gc-sections -Wl,--print-gc-sections ...
项目 | 说明 |
---|---|
作用 | 去除未使用的代码和数据节,减小体积 |
搭配使用 | -ffunction-sections 、-fdata-sections |
风险点 | 重要符号误删、构造函数失效 |
保护方式 | __attribute__((used)) ,链接脚本中 KEEP() |
常用调试参数 | --print-gc-sections |
在做嵌入式或裸机开发,建议编写或调整链接脚本时特别注意使用 KEEP()
保住必要节。
这两个是 GCC 编译器的选项,和代码生成相关,作用如下:
-fno-inline
作用:禁止函数内联(inline)。
默认情况下,GCC 会根据优化等级和函数复杂度尝试内联一些函数(将函数调用展开成函数体代码,减少调用开销,提高速度)。
使用 -fno-inline
后,所有函数都会以正常的调用方式生成,不做内联优化。
适用于:
-fno-align-functions
作用:禁止对函数进行对齐。
默认情况下,GCC 会将函数起始地址对齐到一定字节边界(比如 4 或 16 字节),以提升执行效率(对齐访问更快)。
使用 -fno-align-functions
后,函数不会自动对齐,函数可能从任意地址开始。
适用于:
选项 | 作用 | 使用场景 |
---|---|---|
-fno-inline |
禁止函数内联 | 需要严格函数边界或调试 |
-fno-align-functions |
禁止函数地址自动对齐 | 代码布局控制,减少代码空间 |
-Wall
`` GCC 的 警告选项,用于控制编译器在编译时是否输出某些警告信息:
-Wall
:开启“常用”警告意思:Wall
= “Warn All”,但其实它并不是开启所有警告,而是开启一组常见、重要的警告。
作用:
检查潜在的代码错误或不规范写法,例如:
-Wunused-*
)if (a = b)
) 实际上 -Wall
包括了很多子警告,比如:
-Wunused-variable
-Wunused-function
-Wreturn-type
-Wparentheses
-Wswitch
-Wformat
...
-Wall
作为基础检查。-Wno-unused
:关闭未使用代码的警告意思:关闭所有 -Wunused-*
类别的警告(如变量、参数、函数没用到)。
常见的相关警告包括:
-Wunused-variable
-Wunused-parameter
-Wunused-function
-Wunused-label
用途:当你不想看到未使用变量/参数等的警告时使用,比如:
️ 示例:
int foo(int x, int y) {
(void)y; // 更好的方式:明确告诉编译器 y 未使用
return x * 2;
}
或用编译选项关闭它:
gcc -Wall -Wno-unused ...
选项 | 作用 | 推荐使用 |
---|---|---|
-Wall |
开启一批重要警告,帮你发现常见错误 | ✅ 强烈推荐 |
-Wno-unused |
关闭未使用代码的警告 | ⚠️ 仅在确有需要时 |
开发中比较常见的组合如下:
gcc -Wall -Wextra -Werror -Wno-unused-parameter -g -O0
含义:
-Wall
:基本警告-Wextra
:更严格的额外警告-Werror
:将所有警告当作错误处理(适合 CI)-Wno-unused-parameter
:允许函数参数没用(但保留其它 unused 警告)-Wl
是 GCC 编译器用来向链接器(ld)传递参数的前缀。它的全称是 “Windows linker” 还是啥的没关系,关键是告诉 GCC:
“后面的参数不是给 GCC 本身的,而是直接交给链接器处理。”
gcc main.o -Wl,option1,option2,...
gcc main.o -Wl,-Map=output.map,-T,linker.ld -o output.elf
相当于告诉链接器:
-Map=output.map
—— 生成映射文件-T linker.ld
—— 使用 linker.ld
作为链接脚本-Wl
单独写 -Wl
会导致编译器报错,因为它后面缺少参数。例如:
gcc main.o -Wl
会报错,提示参数不完整。
选项 | 用途 | 示例 |
---|---|---|
-Wl, |
传递 给链接器 ld |
-Wl,-T,linker.ld,-Map=out.map |
-Wl,-L,../ldscripts,-T,linker.ld,-Map=out.map,--strip-debug
--strip-debug
是一个链接器(ld
)选项,用来 从生成的可执行文件或目标文件中删除调试信息。
.debug_*
段),减小文件体积。gdb
)定位代码。.dSYM
或 .debug
文件,主程序剥离调试符号。链接时传给 ld
:
ld --strip-debug -o program program.o
GCC 传递给链接器:
gcc main.o -Wl,--strip-debug -o program
strip
命令对最终文件处理,或者分离调试符号。GCC 本身不能直接输出裸二进制(.bin
)文件,因为它默认输出的是 ELF 格式。但你可以把 arm-none-eabi-objcopy
加到 GCC 编译流程后面,以脚本或一条命令的形式完成整个过程。
&&
连写arm-none-eabi-gcc main.c -T linker.ld -o firmware.elf && \
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
gcc
编译并链接生成 firmware.elf
objcopy
转成裸二进制 firmware.bin
(arm-none-eabi-gcc main.c -T linker.ld -o firmware.elf && \
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin)
这样写不会污染外部 shell 环境,适合写进构建脚本。
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
firmware.elf: main.c
arm-none-eabi-gcc main.c -T linker.ld -o $@
这样你只要执行 make firmware.bin
,就自动编译并转换了。
gcc
输出 .bin
?GCC 只能输出标准可执行格式(ELF、Mach-O、PE 等),不能直接生成裸 .bin
。生成裸二进制是 objcopy
的职责。