c++中的Undefined Behavior (UB) 有多危险? (常见UB案例)

UB 的真正危险在于行为脱离标准约束,导致编译器、平台或优化级别不同而结果迥异,越“稳

定”的表现越易被误判为正确。

访问已释放的内存(use-after-free)

这是最典型的 UB 之一,程序可能看似正常运行,也可能随机崩溃、读到垃圾值,甚至被利用触发远程代码执行。

常见场景包括:用 deletedelete[] 释放指针后继续解引用;容器(如 std::vector)扩容导致迭代器/指针失效后仍使用;返回局部对象地址并后续访问。

  • std::vectorpush_back 可能重分配内存,使原有 &v[0] 或迭代器立即失效
  • 不要写 int* p = new int(42); delete p; return *p; —— 即使它在某次测试中输出 42,也不代表安全
  • UB 不保证崩溃,所以靠“没崩”来验证逻辑是危险的

有符号整数溢出(signed integer overflow)

C++ 标准明确将 intlong 等有符号类型溢出定义为 UB,编译器可基于“它不会发生”做激进优化,导致逻辑被意外删减。

例如,循环条件 i 中若 iint 且持续递增,编译器可能假设 i 永远不会溢出,从而移除边界检查或整个循环。

  • unsigned int 替代(其溢出是定义良好的模运算),但需确认语义允许回绕
  • 对关键计算用 std::add_overflow(C++23)或手动检查,如:if (a > INT_MAX - b) /* 处理溢出 */
  • Clang/GCC 加 -fsanitize=signed-integer-overflow 可在运行时捕获这类 UB

未初始化变量读取(reading uninitialized memory)

读取未初始化的栈变量(如 int x; 后直接用 x)、未初始化的类成员、或 malloc 返回但未 memset 的内存,都属 UB。

它不等于“读到零”或“读到随机数”——编译器可假设该变量“不存在合理值”,进而优化掉依赖它的分支。例如:

int foo() {
    int x;
    if (x > 0) return 1;  // 编译器可能直接删掉这个 if,因为 x 未定义
    return 0;
}
  • 始终显式初始化:用 = {}= 0 或构造函数初始化
  • 避免裸 malloc;优先用 std::vectorstd::unique_ptr 等 RAII 容器
  • 启用 -Wuninitialized-Wmaybe-uninitialized(GCC/Clang)

违反严格别名规则(strict aliasing violation)

当通过非兼容类型指针访问同一块内存时触发,比如用 float* 强制 reinterpret 一个 int 变量,或用 char* 以外的类型绕过类型系统读写。

编译器依赖该规则做寄存器复用和指令重排,UB 可能导致读到旧值、写入被忽略,或产生不可预测的汇编结果。

  • 正确方式是用 std::memcpystd::bit_cast(C++20)实现类型双关
  • 禁止写类似 *(float*)&i = 3.14f; 的代码(即使它在某些平台“工作”)
  • union 在 C++17 前用于类型双关也是 UB;C++17 起仅允许访问最后写入的成员

UB 的真正危险不在崩溃,而在它让程序行为脱离标准约束——编译器、平台、甚至同一编译器的不同优化级别,都可能给出完全不同的结果。越“稳定”的 UB 表现,越容易让人误以为代码正确。