c++oding="utf-8" ?>
必须禁用-fno-exceptions和-fno-rtti,否则虚函数等会向flash注入几kb元数据且ram中每个对象多占4–8字节vptr;cmake中应统一使用target_compile_options(target private -fno-exceptions -fno-rtti),避免add_compile_options误伤第三方库,并配合c++17及以上标准及.map文件验证符号清除效果。

必须禁用 -fexceptions 和 -frtti,否则虚函数、dynamic_cast、typeid 会悄悄往 Flash 里塞几 KB 的元数据,且 RAM 中每个对象多占 4–8 字节 vtable 指针。
如何在 CMake 中全局关闭异常和 RTTI
嵌入式项目几乎从不使用 C++ 异常语义(throw/catch)和运行时类型查询(dynamic_cast),但默认开启。CMake 中需显式关闭:
- 对所有 target 添加
target_compile_options(target PRIVATE -fno-exceptions -fno-rtti) - 若使用
add_compile_options(),注意它作用于全局,可能误伤第三方库(如某些 HAL 库头文件含dynamic_cast用法,编译会失败) - 务必配合
set(CMAKE_CXX_STANDARD 17)或更高,避免因标准降级触发隐式启用 RTTI(GCC 12+ 在c++17下仍默认开 RTTI) - 检查生成的
.map文件:搜索__cxxabiv1、typeinfo、vtable等符号,若仍有大量出现,说明未生效或被子目录 CMakeLists 覆盖
为什么只加 -fno-exceptions 还不够
禁用异常后,std::exception、std::terminate 等符号仍可能被链接进来——尤其当你用了 std::vector 或其他 STL 容器(即使没 throw,部分实现会在构造失败时调用 std::terminate)。这时要:
- 显式定义
extern "C" void __cxa_pure_virtual() { while(1); }并确保链接进去(防止未定义引用) - 用
-fno-use-cxa-atexit避免插入全局析构注册代码 - 若用 picolibc 或自定义 libc,确认其
libstdc++_nano或libcxxabi_nano已裁剪掉异常相关 stub
RTTI 关闭后 dynamic_cast 和 typeid 会怎样
它们不会静默降级为等效逻辑,而是直接编译失败:
-
dynamic_cast:报错error: cannot use 'dynamic_cast' with -fno-rtti -
typeid:报错error: 'typeid' used on type 'X' with -fno-rtti - 但虚函数调用(
obj->func())完全不受影响,vtable 本身仍存在,只是去掉了类型名、基类链等冗余信息 - 如果你真需要运行时类型识别,应改用轻量方案:比如给基类加
enum class TypeID成员 +virtual TypeID get_type() const = 0,零额外 Flash 开销
容易忽略的隐式启用点
有些写法看似没用异常/RTTI,实则触发编译器悄悄打开它们:
- 包含
<memory></memory>后使用std::shared_ptr:即使没抛异常,其销毁逻辑依赖std::bad_weak_ptr,会拉入异常 ABI - 使用
std::string或std::vector的默认构造:某些 libstdc++ 实现中,容量分配失败路径仍预留了throw分支 - CMake 中
find_package(Threads)后链接Threads::Threads:部分工具链会顺带启用-pthread,间接影响异常处理模型 - 第三方库(如 CMSIS-NN)的 C++ 封装头:若其内部用了
dynamic_cast,而你又没用-isystem把它标为系统头,则编译会卡在这儿
最稳妥的做法是:先加 -fno-exceptions -fno-rtti,再逐个取消注释疑似高危头文件,看编译是否失败;失败处就是隐式依赖点。Flash 省下的每 KB,都来自对这些“看不见的依赖”的清理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











