VEH优先级高于SEH和SetUnhandledExceptionFilter,可于异常分发第一阶段拦截段错误,支持多线程自动继承、DLL初始化期注册及链式调用,而SetUnhandledExceptionFilter仅为进程级兜底且存在竞态与时机限制。

Windows 下 C++ 程序发生段错误(如空指针解引用、越界写入)时,__try/__except 无法捕获——它只处理 SEH 异常,而非法内存访问在用户态触发的是硬件异常,需靠向量化异常处理(VEH)才能在任意线程、任意上下文(包括 DLL 初始化期)第一时间拦截。
为什么 SetUnhandledExceptionFilter 不够用
SetUnhandledExceptionFilter 是进程级兜底,仅在异常未被任何 SEH 框架(__try)、未被 VEH 处理时才触发;且多线程下存在竞态:若异常发生在主线程刚注册但子线程尚未同步完成时,可能漏捕。VEH 则通过 AddVectoredExceptionHandler 注册,优先级高于 SEH,且每个线程自动继承,无注册时机依赖。
- VEH 回调在异常分发第一阶段就被调用,甚至早于栈展开(stack unwinding),能拿到原始上下文(
EXCEPTION_POINTERS*) -
SetUnhandledExceptionFilter无法在 DLL 的DLL_PROCESS_ATTACH中安全使用(微软明确标注“不推荐”),VEH 可以 - VEH 支持多个处理器按注册顺序链式调用,便于模块化异常诊断(比如日志模块 + 崩溃转储模块各注册一个)
AddVectoredExceptionHandler 必须设为 TRUE 才生效
第二个参数是布尔值,控制是否插入到链表头部:TRUE 表示最高优先级(最先调用),FALSE 插入尾部(最后调用)。段错误必须抢在系统默认处理前拦截,否则进程直接终止——所以几乎总是传 TRUE。
LONG WINAPI MyVEH(PEXCEPTION_POINTERS ExceptionInfo) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// 记录地址、读写类型、触发指令地址
OutputDebugString(L"AV caught via VEH\n");
return EXCEPTION_EXECUTE_HANDLER; // 终止异常传播,避免崩溃
}
return EXCEPTION_CONTINUE_SEARCH; // 交给下一个 VEH 或 SEH
}
// 正确注册方式:
AddVectoredExceptionHandler(TRUE, MyVEH);
- 返回
EXCEPTION_EXECUTE_HANDLER后,系统不会继续查找其他处理器,也不会展开栈——你得自己确保现场可安全恢复(比如只是记录日志后退出) - 若想记录后仍让程序崩溃(例如生成 minidump),应返回
EXCEPTION_CONTINUE_SEARCH,并在后续用SetUnhandledExceptionFilter补充 dump 逻辑 - 注册后务必检查返回值是否非
NULL,注册失败(如内存不足)时函数返回NULL
捕获 EXCEPTION_ACCESS_VIOLATION 后怎么定位非法地址
ExceptionInfo->ExceptionRecord->ExceptionInformation[0] 表示访问类型(0=读,1=写),[1] 是出错的内存地址。这个地址极关键——但它可能是合法地址(比如已释放的堆块),也可能是明显非法值(如 0x00000000 或 0xFFFFFFFF)。
- 不要直接
printf("%p", ExceptionInfo->ExceptionRecord->ExceptionInformation[1])——该字段是ULONG_PTR,32/64 位宽度不同,强制转void*在 x64 下会截断高位 - 正确做法:
printf("AV at 0x%I64x", (uint64_t)ExceptionInfo->ExceptionRecord->ExceptionInformation[1]) - 注意:该地址是触发异常的指令试图访问的地址,不一定是 bug 根源(比如数组越界后写到了相邻变量,真正问题在前面的循环边界)
VEH 不能替代代码健壮性,且有隐蔽陷阱
VEH 拦住段错误不等于程序能继续运行——栈可能已损坏,寄存器状态不可信,C++ 对象析构器不会执行。它唯一可靠用途是故障快照(地址、线程 ID、模块列表、寄存器快照)和受控退出。
- 在 VEH 回调里调用
malloc、std::string构造、或任何可能触发新异常的 API 是高危行为——此时堆可能已破坏,CRT 状态不确定 - 不要尝试从 VEH 中调用
MiniDumpWriteDump——它内部会分配内存、加载 dbghelp.dll,极易引发二次异常;应预先加载并缓存句柄,或改用更轻量的WriteProcessMemory+ 自定义 dump - VEH 对
EXCEPTION_STACK_OVERFLOW无效——栈溢出时已无可用栈空间执行你的回调,必须靠编译器选项(/STACK)和 guard page 预警
VEH 是把双刃剑:它让你看见崩溃前最后一帧,但也容易让人误以为“能恢复”。真实项目中,它只该出现在 crash reporter 或调试辅助模块里,而非主业务逻辑的容错手段。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











