当前位置: 首页 > backend >正文

STM32开发GCC常用编译选项

-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 <file>指定输出文件名(可用于 .o 或可执行)gcc -c main.c -o main.o

❗注意事项

情况说明
忘记加 -c 但用了 -o xxx.o编译器会尝试链接,报错找不到 main()_start()
没加 -o默认输出为 a.out(可执行)或 main.o(目标文件)

🧠 示例完整构建流程(Cortex-M)

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c main.c -o main.o

这里的意思是:

  • 针对 ARM Cortex-M3 架构编译
  • 编译 main.c 生成 main.o,不进行链接

如果你在用 Makefile,或者想把多个 .c 编译成 .o 再链接为 .elf,需要 -c - o


-I 是 GCC 中用于指定 头文件(header file)搜索路径 的编译选项,是 C/C++ 项目构建中必不可少的一部分。


✅ 语法

-I<路径>

或者加空格也可以(不推荐):

-I <路径>

🎯 功能说明

告诉编译器:当遇到 #include 指令时,要去指定的路径找头文件

适用于:

  • 自己写的头文件(放在 include/inc/ 等目录)
  • 第三方库的头文件(如 CMSIS、FreeRTOS、HAL 等)

🧠 用法示例

gcc -I./include -I../drivers -c main.c -o main.o

遇到这段代码时:

#include "my_driver.h"

编译器将按顺序查找:

  1. 当前文件所在目录
  2. ./include/my_driver.h
  3. ../drivers/my_driver.h
  4. 系统默认头文件目录(如 /usr/include

🧩 #include 两种写法的区别:

写法搜索顺序
#include "file.h"当前源文件目录 → -I 指定路径 → 系统默认路径
#include <file.h>-I 指定路径 → 系统默认路径(不查当前目录)

📌 所以,你用双引号 " 时更适合项目内文件,尖括号 < > 适合标准库或第三方库。


🛠 常见使用场景

✅ 1. 组织良好的项目结构

project/
├── include/
│   └── foo.h
├── src/
│   └── foo.c

编译命令:

gcc -I./include -c src/foo.c -o foo.o

✅ 2. 使用第三方库(如 FreeRTOS)

-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 架构的代码
  • 使用该架构支持的指令集(ARMv7-M)
  • 启用/禁用该 CPU 支持的硬件特性(如是否有 FPU、DSP 指令等)

⚙️ 背景:

Cortex-M3 是一个典型的低功耗、低成本嵌入式处理器,广泛用于微控制器(如 STM32F1 系列)。


🔹 -mthumb

✅ 作用:

强制编译器生成 Thumb 指令集 而不是传统的 ARM 指令集。

🔍 什么是 Thumb?

  • Thumb 是 ARM 指令集的子集,使用 16 位压缩指令(后来也支持 32 位扩展指令)。

  • 与标准 32 位 ARM 指令相比:

    • ✅ 优点:体积小、执行快(特别在低带宽内存系统中)
    • ⚠️ 缺点:功能不如完整 ARM 指令丰富

✅ Cortex-M 系列强制使用 Thumb:

  • Cortex-M3 只支持 Thumb,不支持传统 ARM 模式。
  • 所以在编译 Cortex-M3 时,必须加 -mthumb,否则链接时会出错(因为生成的是 ARM 指令,而硬件不支持)。

🔧 一起使用示例:

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -O2 -Wall -c main.c -o main.o

说明:

  • 使用适合 Cortex-M3 的 CPU 特性
  • 编译成 Thumb 指令(Cortex-M3 强制)
  • 开启优化和警告

⚠️ 注意事项:

问题说明
必须配合 -mthumbCortex-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 寄存器传递

⚠️ ABI 不兼容的风险:

  • hardsoft / softfp 不能混合链接

  • 所以你必须确保:

    • 编译的所有代码(特别是中间库文件 .a, .o)用的是相同的 -mfloat-abi 选项!

🔧 实际组合推荐(例如 Cortex-M4F):

-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard

🔍 编译器如何选择浮点方式?

CPU 是否带 FPU-mfpu-mfloat-abi是否使用硬件浮点ABI 兼容性
❌ Cortex-M3不加或 nonesoft与纯整数 ABI 相同
✅ Cortex-M4Ffpv4-sp-d16softfp✅ 用硬件计算✅ 与软浮点兼容
✅ Cortex-M4Ffpv4-sp-d16hard✅ 用硬件计算⚠️ 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 下的行为

  1. 不强制要求定义 main()

    • 可以自己定义入口点(如 _start, Reset_Handler, boot_entry 等)
  2. 不假设标准头文件存在

    • <stdio.h>, <stdlib.h> 等可能无法使用,除非你自己提供它们。
  3. 不会自动链接 libc

    • 不链接标准的启动文件(crt0.o
    • 不自动使用 exit() 结尾

✅ 示例:Cortex-M 裸机编译命令

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<ldscript>指定自定义链接脚本(用于控制内存布局等)

✅ 推荐使用场景

  • 嵌入式裸机开发(如 STM32、ESP32 ROM 启动)
  • 引导加载器(bootloader)
  • 操作系统内核(如 U-Boot、Linux kernel)
  • 编写最小 runtime 环境或 RTOS 内核
  • 写自己的 startup.S + main.c,不依赖标准 crt0.o

🧠 小结

编译器选项说明
-ffreestanding禁用标准 C 运行时假设,适合裸机/操作系统等底层开发
必须手动配置启动代码、入口函数、链接脚本、异常向量表等均要手动指定


-O0-O2-Os 是 GCC 的 编译优化等级选项,用来控制编译器如何生成代码,分别代表不同的优化目标。


🔹 1. -O0无优化

  • 功能:完全关闭优化(默认)。

  • 特点

    • 保留源代码结构,变量和函数都如源码所示。
    • 非常适合调试(变量名完整、调试步进准确)。
    • 代码运行慢、体积大。
  • 适用场景:调试阶段、开发初期


🔹 2. -O2速度优化

  • 功能:启用大多数优化选项,不影响编译时间太多。

  • 优化目标提高程序运行速度

  • 常见优化

    • 消除死代码(Dead Code Elimination)
    • 循环优化
    • 内联函数
    • 常量传播
  • 适用场景:通用程序开发、对性能有要求的应用


🔹 3. -Os空间优化

  • 功能:开启与 -O2 类似的优化,但会禁用会让代码变大的优化(如循环展开)。
  • 优化目标减小程序体积
  • 适用场景:嵌入式系统、Flash 或 ROM 空间有限、目标平台资源受限

📊 对比总结表

优化等级优化目标编译速度程序运行速度可执行文件大小调试友好常见用途
-O0无优化✅ 最友好调试、开发初期
-O2提高速度中等✅ 快一般性能敏感应用
-Os减小体积中等快(略慢于 -O2✅ 小一般嵌入式、ROM 限制环境

🔧 实际开发中的组合建议

使用目标推荐编译选项
调试程序-O0 -g
发布性能优先-O2 -march=native(可再加 -flto
发布体积优先(嵌入式)-Os -ffunction-sections -fdata-sections -Wl,--gc-sections

❗注意事项

  • 调试信息:高优化等级(-O2, -Os)可能影响调试体验(如变量消失、断点跳转不准确)。加 -g 可以保留调试符号。
  • 行为改变:某些未定义或不稳定代码行为在优化后会出现不同表现,需注意编码规范。

–gc-sections

--gc-sections 是 GNU 链接器 (ld) 的一个选项,用于 去除未使用的节(section),从而 减小最终可执行文件或固件的大小。它在嵌入式开发中非常常用,尤其是资源受限的系统。


一、什么是 --gc-sections

--gc-sections(Garbage Collection of Sections)告诉链接器:

“将没有被引用的代码段和数据段清理掉(移除)”。


二、工作机制

这个机制依赖于:

1. 编译器的 -ffunction-sections-fdata-sections 选项
  • -ffunction-sections:将每个函数放入独立的 .text.<funcname> 段中。
  • -fdata-sections:将每个全局变量放入独立的 .data.<varname>.bss.<varname> 段中。

这样可以让链接器更精细地控制每个函数/变量的去留。

2. 链接器的 --gc-sections
  • 链接器从入口点(如 _startmain)开始遍历引用关系。
  • 所有 没有被直接或间接引用的节 都被视为“垃圾”,可以被移除。

三、用法示例

编译阶段:
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


四、使用注意事项

  1. 必须配合 -ffunction-sections -fdata-sections 才有效
    否则多个函数/数据仍在一个段里,链接器无法单独识别每个函数的可达性。

  2. ⚠️ 静态初始化或特殊节(如中断向量、构造函数)可能被误删

    • 比如没有被显式引用的中断处理函数可能被清除。
    • 使用 __attribute__((used))KEEP() 在链接脚本中保护这些符号。

    示例:

    KEEP(*(.isr_vector))
    KEEP(*(.init_array))
    
  3. ⚠️ 链接脚本要支持节级保留或精细控制

    • 自定义链接脚本中如果使用 *(.text) 而不是 KEEP(*(.text.funcname)),可能导致重要代码被清除。
  4. 某些语言特性或库依赖反射/动态行为,不能轻易剔除

    • 如 C++ RTTI、虚函数表、某些库通过注册机制访问符号。

五、如何排查 --gc-sections 导致的错误?

  • 链接失败或运行时出现异常行为,怀疑是符号被移除?
    可临时禁用 --gc-sections 或加 __attribute__((used)) 进行验证。

  • 使用 objdump -tnm 等工具查看符号是否还在。

  • -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
  • 用途:当你不想看到未使用变量/参数等的警告时使用,比如:

    • 写 API 函数时某些参数暂时未用
    • 临时注释掉代码进行测试

🛠️ 示例:

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,<args>传递 <args> 给链接器 ld-Wl,-T,linker.ld,-Map=out.map

-Wl,-L,../ldscripts,-T,linker.ld,-Map=out.map,--strip-debug


--strip-debug 是一个链接器(ld)选项,用来 从生成的可执行文件或目标文件中删除调试信息


详细说明

  • 功能:去除 ELF 文件中的调试符号(.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 环境,适合写进构建脚本。


✅ Makefile 中示例

firmware.bin: firmware.elfarm-none-eabi-objcopy -O binary $< $@firmware.elf: main.carm-none-eabi-gcc main.c -T linker.ld -o $@

这样你只要执行 make firmware.bin,就自动编译并转换了。


🚫 为什么不能直接让 gcc 输出 .bin

GCC 只能输出标准可执行格式(ELF、Mach-O、PE 等),不能直接生成裸 .bin。生成裸二进制是 objcopy 的职责。


http://www.xdnf.cn/news/13714.html

相关文章:

  • 计组刷题日记(1)
  • 快速熟悉公司的服务器开发环境需要系统
  • 软件测试之APP测试要点(包含Monkey基础使用)
  • 如何创建vue工程?以及遇到问题的解决方法
  • vue3提供的hook和通常的函数有什么区别
  • Python直接访问Windows API库之pywin32使用详解
  • mysql递归查询所有父节点拼接父节点名称
  • 使用Gradle打包springboot项目为JAR包教程
  • 蓝凌EKP产品:低门槛、可扩展、可视化公式引擎应用示例
  • 功能化组件编码流程-2(延续上一章)
  • 《HarmonyOSNext属性动画实战手册:让UI丝滑起舞的魔法指南》
  • 人工智能新范式:从大模型到智能体的演化逻辑
  • 语音信号处理三十——高效多相抽取器(Polyphase+Noble)
  • Java并发编程实战 Day 18:线程池深度剖析与自定义实现
  • 工业自动化网关在饮料行业中的应用:DeviceNet转Modbus RTU协议转换网关案例
  • sssssssssssss
  • 电子电路原理第十八章(有源滤波器)
  • 【C#如何计算从某一个日期到今天过了多少天】2022-4-24
  • 94. 评论日记
  • Linux CPU 亲和性
  • ARM架构下安装mysql8.0
  • Dagster软件定义资产(SDA)完全指南:从概念到落地实践
  • 研发效能提升--质量改进完美闭环
  • TTS走向拟人化时代:数据堂高质量语音资源全面支撑模型升级
  • 库架一体式货架:重塑现代仓储空间的智能解决方案
  • 简单的五子棋实现简介
  • 【【大模型训练】中短序列attention 和MOE层并行方式】(三)
  • 从编辑到安全设置: 如何满足专业文档PDF处理需求
  • 汇编字符串比较函数
  • yapi服务端可视化安装