启用 /std:c++17 和 /permissive- 可激活 MSVC 对未定义行为的早期拦截,强制标准合规并支持 GSL 工具(如 gsl::span、gsl::not_null),使越界访问、空指针解引用等在编译期或 debug 断言阶段暴露,而非运行时崩溃。

用 /std:c++17 + /permissive- 激活编译器对UB的早期拦截
MSVC 默认宽松兼容旧代码,很多未定义行为(比如数组越界读、reinterpret_cast 跨类型别名)只发 warning 甚至不报。启用 /permissive- 强制标准合规后,std::vector::operator[] 在 debug 模式下会自动检查索引,而 /std:c++17 是启用 Core Guidelines 支持的前提——否则 gsl::span 和 gsl::not_null 根本无法使用。
-
/permissive-会让类似int arr[3]; arr[5] = 0;这种直接数组访问在编译期就报错(如果配合静态分析),而不是等到运行崩溃 - 必须搭配
/D "_ITERATOR_DEBUG_LEVEL=2"(Windows Debug)才能让 STL 容器触发越界断言 - Clang/LLVM 用户对应的是
-std=c++17 -fsanitize=undefined,但注意它不能替代编译器级约束,只是运行时补漏
把裸指针换成 gsl::not_null 和 gsl::span
Core Guidelines 的核心价值不是“多写几行”,而是把隐含假设变成编译期契约。比如函数参数声明为 gsl::not_null,调用方传入 nullptr 会直接编译失败;用 gsl::span 替代 int* + size_t 组合,能防止长度与指针脱节导致的越界。
-
gsl::span构造时若传入空指针且 size > 0,debug 版本会断言失败——这比 segfault 提前几十个调试周期 - 不要对
gsl::span做reinterpret_cast类操作,它不保证底层内存连续性语义(虽然当前实现是,但规范没承诺)(span.data()) -
gsl::not_null不增加运行时开销,但禁止你写if (p == nullptr)这类防御逻辑——这不是省事,是删掉错误前提
用 clang++ -fsanitize=address,undefined 捕获运行时 UB
编译器静态检查再严,也覆盖不了所有路径分支。ASan(AddressSanitizer)和 UBSan(UndefinedBehaviorSanitizer)是 Windows / Linux / macOS 通用的运行时探测手段,尤其擅长发现:释放后使用、栈缓冲区溢出、有符号整数溢出、未初始化变量读取。
clang++ -std=c++17 -fsanitize=address,undefined -g main.cpp -o main ./main ==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x00010d9a8c3e bp 0x7ffee3b4a9a0 sp 0x7ffee3b4a998 READ of size 4 at 0x602000000010 thread T0
- UBSan 对
int x = INT_MAX; ++x;这类操作会打印明确提示,而不是静默翻转 - ASan 会显著拖慢执行速度(2~3倍),但它是目前最可靠的堆/栈内存 UB 探测器
- 注意:Windows 上需用 Clang for Windows 或 MSVC 17.5+ 才支持完整 UBSan;MinGW 不支持 ASan
禁用 std::shared_ptr::reset() 隐式转换构造,堵住循环引用漏洞
Core Guidelines 规则 R.32 明确反对用 std::shared_ptr 管理对象图中的双向引用,因为 reset() 允许临时绕过所有权契约。一个典型陷阱是:parent->children.push_back(std::shared_ptr——这里 child 是裸指针,构造 shared_ptr 时会新建控制块,导致 parent 和 child 各自持有一份独立引用计数。
立即学习“C++免费学习笔记(深入)”;
- 改用
std::enable_shared_from_this,并统一通过shared_from_this()获取子节点的shared_ptr - 在 CMake 中加
add_compile_options(-Wno-unused-variable)之前先确认:这个 warning 很可能暴露了你忘了用shared_from_this()的地方 - 更彻底的做法是删掉所有裸
new Node,改用std::make_shared——它能避免控制块与对象内存分离带来的额外分配开销,也天然杜绝了裸指针误传()
/permissive-,或只用 gsl::span 却放任裸指针在其他模块流通,UB 依然会在缝隙里存活。









