未定义行为指编译器可任意处理,如正常运行、删除代码或表现不一;悬空指针解引用、有符号整数溢出均为典型例子,应使用智能指针和地址 sanitizer 防范。

未定义行为不是“随机崩溃”,而是编译器可以合法地做任何事——包括看起来正常运行、优化掉关键代码、或在不同编译器/优化等级下表现完全不同。
访问已释放的内存(delete 后继续用指针)
这是最常被低估的坑:指针变成“悬空指针”后,if (ptr != nullptr) 依然可能为真,但解引用 *ptr 或调用 ptr->method() 就是未定义行为。
- 不要靠
nullptr检查来防御已释放内存——释放后应立即置为nullptr(仅对裸指针有效) - 优先用
std::unique_ptr或std::shared_ptr,它们在析构时自动置空(对weak_ptr仍需lock()) -
-fsanitize=address可捕获多数悬空访问,但不能覆盖所有情况(比如释放后立即读栈上残留值)
有符号整数溢出(int 加法超出范围)
C++ 标准明确将有符号整数溢出列为未定义行为;而 unsigned int 溢出是定义良好的模运算。
- 别写
int x = INT_MAX; x++;—— 看似简单,但编译器可能据此删除后续判断(如if (x 被整个优化掉) - 需要安全算术时,用
std::add_overflow(C++23)或第三方库如absl::CheckAdd;旧标准可用__builtin_add_overflow(GCC/Clang) - 注意:
-ftrapv仅在溢出时触发信号,不改变语义,且不适用于所有平台
读未初始化的局部变量(int x; 直接使用)
内置类型(int、double、自定义结构体等)在栈上默认不初始化,其值是“垃圾值”,读取即 UB。
立即学习“C++免费学习笔记(深入)”;
- 类成员变量若在构造函数中漏掉初始化列表(如
MyClass() { x = 0; }),x在赋值前已被读?不一定,但风险极高 - 启用
-Wuninitialized和-Wmaybe-uninitialized(Clang/GCC 都支持),但它们是启发式检测,会漏报 - 更可靠的是:用
= default构造 + 成员变量就地初始化(int x{0};),或统一走初始化列表
违反严格别名规则(reinterpret_cast 乱转指针类型)
例如把 float* 强转成 int* 再解引用,除非通过 char* 或 std::memcpy,否则是 UB。
- 错误写法:
int i = *reinterpret_cast<int>(&f);</int>—— 即使在 x86 上跑通,-O2下可能被优化成任意结果 - 正确做法:用
std::memcpy(&i, &f, sizeof(i));,现代编译器会内联为单条指令,无性能损失 -
std::bit_cast(C++20)是更干净的替代,但要求类型大小一致且可平凡复制
真正难防的不是那些明显危险的操作,而是 UB 在低优化等级下“恰好工作”,一开 -O2 就逻辑错乱;调试器也帮不上忙——因为它根本不是 bug,而是编译器眼中的“合法自由”。










