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 优化选项的层次结构
为了让你更直观地理解,我画了一张图。这张图展示了编译选项从工具链文件到最终编译命令的传递路径。
从这张图你可以看到,编译选项是分层叠加的。工具链文件里的选项是基础,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 总结一下
编译选项控制,说白了就是三件事:
- 把架构相关的选项放在工具链文件里
- 把项目相关的选项放在CMakeLists.txt里
- 用构建类型控制优化级别和调试信息
做到这三点,你的交叉编译项目就不会在编译选项上出大问题。当然,具体到每个芯片,可能还有一些特殊的选项,比如-mno-unaligned-access之类的,这个就需要你去看芯片手册和编译器文档了。
好了,这一节就到这里。记住:编译选项是给编译器指路的,指错了路,编译器就跑偏了。