12、编译选项控制:CMAKE_C_FLAGS、CMAKE_CXX_FLAGS、针对目标架构的优化选项。

各位同学,咱们今天聊点实在的。编译选项控制,说白了就是告诉编译器:「嘿,给我按这个路子来编译!」。在嵌入式交叉编译里,这步要是搞错了,轻则性能拉胯,重则直接编译不过,甚至跑起来就崩。

我个人习惯,拿到一个新板子,第一件事不是急着写代码,而是先把编译选项理清楚。你想想看,同样的C代码,给x86编译和给ARM Cortex-M编译,那能一样吗?

12.1 CMAKE_C_FLAGS 与 CMAKE_CXX_FLAGS:基础中的基础

这两个变量,是CMake里控制编译器参数的核心。CMAKE_C_FLAGS管C编译器,CMAKE_CXX_FLAGS管C++编译器。它们可以出现在三个层级:

  • CMAKE_C_FLAGS:全局C编译选项,影响所有目标
  • CMAKE_CXX_FLAGS:全局C++编译选项,影响所有目标
  • CMAKE_C_FLAGS_DEBUG / CMAKE_CXX_FLAGS_RELEASE:特定构建类型的附加选项

我在项目中遇到过,有人把调试信息-g和优化-O2混在一起用,结果调试时单步执行跳得乱七八糟。嗯,这里要注意:调试和优化的选项,最好分开配置。

核心原则:全局选项放CMAKE_C_FLAGS,类型特定选项放CMAKE_C_FLAGS_。别一股脑全塞进去。

# 设置全局C编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra")

# 设置Debug模式附加选项
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0 -DDEBUG")

# 设置Release模式附加选项
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -DNDEBUG")

12.2 针对目标架构的优化选项:别让编译器瞎猜

交叉编译时,最忌讳的就是让编译器「自动检测」架构。你想想看,你的宿主机是x86,目标板是ARM,编译器自动检测出来的全是x86的优化,那不就乱套了吗?

常见的架构优化选项包括:

架构 常用优化选项 说明
ARM Cortex-M4 -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard 带FPU的M4,硬浮点
ARM Cortex-A72 -mcpu=cortex-a72 -mtune=cortex-a72 -O2 64位应用处理器
RISC-V RV32IMC -march=rv32imc -mabi=ilp32 32位,带乘法和压缩指令
MIPS32 -march=mips32 -mtune=mips32 MIPS架构

我曾经踩过一个坑:给Cortex-M4编译,忘了加-mfloat-abi=hard,结果编译器用软浮点库,生成的二进制大了30%,跑起来还慢。从那以后,我每次写工具链文件,都会把架构选项列得清清楚楚。

警告: -march、-mcpu、-mtune这三个选项不要混用。一般用-mcpu就够了,它会自动选择最合适的指令集和调度策略。如果你非要手动指定-march,记得确认你的编译器支持这个架构。

12.3 在工具链文件中配置编译选项

我个人习惯,把架构相关的选项放在工具链文件里,把项目相关的选项放在CMakeLists.txt里。这样分工明确,复用性也好。

# toolchain.cmake 片段
# 目标系统
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m4)

# 编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

# 架构优化选项 - 这里放的是「这个芯片必须的」选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")

# 链接选项
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")

然后在项目的CMakeLists.txt里,只加项目相关的选项:

# CMakeLists.txt 片段
# 项目相关的编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -fno-exceptions -fno-rtti")

# 优化级别 - 由构建类型控制
# 用户通过 -DCMAKE_BUILD_TYPE=Release 来选择

12.4 优化选项的层次结构

为了让你更直观地理解,我画了一张图。这张图展示了编译选项从工具链文件到最终编译命令的传递路径。

编译选项层次结构 工具链文件 (toolchain.cmake) 架构选项:-mcpu, -mthumb, -mfloat-abi CMakeLists.txt 项目选项:-Wall, -Wextra, -std=c11 构建类型 (Debug/Release) 类型选项:-g -O0 / -O2 -DNDEBUG 最终编译命令 arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb ... -Wall ... -g -O0 ... main.c 层级1 层级2 层级3 层级4 芯片相关 项目相关 调试/发布

从这张图你可以看到,编译选项是分层叠加的。工具链文件里的选项是基础,CMakeLists.txt里的选项是补充,构建类型再根据Debug或Release做调整。最终,这三层选项会合并成一条完整的编译命令。

12.5 避坑指南:我曾经踩过的坑

讲几个我亲身经历过的坑,希望能帮你省点时间:

  • 坑一:忘记设置CMAKE_SYSTEM_PROCESSOR。有一次我交叉编译,忘了设这个变量,结果CMake自动检测到x86_64,生成的编译选项全是错的。编译倒是过了,但跑起来直接Segmentation Fault。
  • 坑二:优化选项冲突。我在工具链文件里设了-O2,又在CMakeLists.txt里设了-Os,结果编译器报warning,最后用了-Os。嗯,这里要注意:后设置的选项会覆盖前面的。
  • 坑三:浮点ABI不匹配。这个我前面提过,软浮点和硬浮点混用,链接时会报一堆undefined reference。解决办法:整个项目统一用同一个浮点ABI。

小技巧: 如果你不确定当前编译选项是什么,可以在CMakeLists.txt里加一行:

message(STATUS "CMAKE_C_FLAGS = ${CMAKE_C_FLAGS}")

这样cmake配置时就会打印出来,一目了然。

12.6 总结一下

编译选项控制,说白了就是三件事:

  1. 把架构相关的选项放在工具链文件里
  2. 把项目相关的选项放在CMakeLists.txt里
  3. 用构建类型控制优化级别和调试信息

做到这三点,你的交叉编译项目就不会在编译选项上出大问题。当然,具体到每个芯片,可能还有一些特殊的选项,比如-mno-unaligned-access之类的,这个就需要你去看芯片手册和编译器文档了。

好了,这一节就到这里。记住:编译选项是给编译器指路的,指错了路,编译器就跑偏了。

公众号:蓝海资料掘金营,微信 deep3321