两种世界POSIX信号 vs Windows结构化异常程序运行中总会遇到异常情况——访问无效内存段错误、除零、非法指令、堆栈溢出。操作系统需要一种机制来通知进程这些异步事件并给它一个处理的机会。POSIX使用信号SignalsWindows使用结构化异常处理SEH, Structured Exception Handling。两者在概念上有对应关系但在机制和使用方式上截然不同。POSIX信号是Unix系统V的遗产经过POSIX标准化后成为所有Unix变体的标准。信号本质上是一个软件中断——内核向进程发送一个信号进程可以选择忽略、默认处理通常是终止进程或使用自定义的信号处理函数。常见信号包括SIGSEGV无效内存访问、SIGABRTabort()调用、SIGFPE算术异常、SIGILL非法指令、SIGTERM终止请求、SIGINTCtrlC中断。Windows的SEH使用__try/__except结构注意这是MSVC的关键字并非标准C的try/catch提供了一个更加结构化的异常处理框架。SEH可以与C异常catch(...)互操作虽然默认行为是将SEH异常转换为C异常。Windows上还有一个独立的向量化异常处理VEH, Vectored Exception Handler机制允许注册一个在SEH之前被调用的处理器链。信号处理的安全限制信号处理函数中能做的事情极其有限。POSIX标准明确规定信号处理函数中只能安全地调用异步信号安全async-signal-safe的函数。这个列表很短write、read、close、_exit、abort、signal、sem_post部分实现和少数几个系统调用。特别注意——printf、malloc、fopen、new、C流操作都不是异步信号安全的。在信号处理函数中调用printf可能死锁——假设主程序正在执行printf并持有stdout的锁此时信号到达信号处理函数再次调用printf并尝试获取同一个锁就会形成死锁。同理malloc内部有锁——在信号处理函数中分配内存可能导致死锁或数据结构破坏。正确做法是指定一个信号处理器用极短的信号安全代码将上下文写入预先分配的缓冲区或只设置一个volatile sig_atomic_t标志然后立即返回。主循环中检查这个标志来执行实际的错误处理逻辑。#includecsignal#includeatomic#includeunistd.h// 异步信号安全的标志volatilesig_atomic_t g_crash_signal0;volatilesig_atomic_t g_crash_count0;// 预分配的缓冲区信号处理函数中不能mallocstaticcharg_crash_stack[8192];externCvoidcrash_handler(intsig,siginfo_t*info,void*context){g_crash_signalsig;g_crash_count;// 第二次崩溃直接退出防止死循环if(g_crash_count1){_exit(128sig);}// 只使用异步信号安全的 write 系统调用constchar*msgFatal signal received, capturing...\n;write(STDERR_FILENO,msg,strlen(msg));// 尝试在信号处理函数中写crash dump// 注意这冒险但常见因为这是最后一搏capture_crash_info_signal_safe(sig,info,g_crash_stack,sizeof(g_crash_stack));}voidinstall_crash_handlers(){structsigactionsa{};sa.sa_sigactioncrash_handler;sa.sa_flagsSA_SIGINFO|SA_ONSTACK;// SA_ONSTACK: 使用单独的堆栈sigaction(SIGSEGV,sa,nullptr);sigaction(SIGABRT,sa,nullptr);sigaction(SIGFPE,sa,nullptr);sigaction(SIGILL,sa,nullptr);sigaction(SIGBUS,sa,nullptr);// 忽略 SIGPIPE向已关闭socket写入时触发signal(SIGPIPE,SIG_IGN);}SA_ONSTACK标志至关重要——它告诉内核使用sigaltstack()配置的备用堆栈来执行信号处理函数。否则如果崩溃本身是由堆栈溢出Stack Overflow引起的信号处理函数也会因为无法扩展堆栈而失败。跨平台崩溃捕获策略对于跨平台项目处理崩溃信号有三种策略策略一使用Google Breakpad或CrashpadBreakpadGoogle开发Chromium项目使用和CrashpadBreakpad的继任者是专业的跨平台崩溃报告库。它们在进程内嵌入一个崩溃处理器在崩溃时生成minidump一种跨平台的崩溃转储格式然后将minidump上传到服务器进行分析。Breakpad的设计经过了全球数亿Chrome浏览器的验证。在Windows上它捕获SEH异常在Linux上捕获POSIX信号在macOS上捕获Mach异常比POSIX信号更底层可以捕获更多的崩溃类型。生成的minidump可以用Breakpad或Crashpad的工具链解析——包括提取调用栈、寄存器值、加载的模块列表、线程信息等。策略二平台特定的崩溃转储在Windows上可以注册一个未处理异常过滤器#ifdef_WIN32#includewindows.h#includedbghelp.hLONG WINAPIunhandled_exception_filter(EXCEPTION_POINTERS*ex_info){// 创建minidump文件HANDLE dump_fileCreateFileA(crash.dmp,GENERIC_WRITE,0,nullptr,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);if(dump_file!INVALID_HANDLE_VALUE){MINIDUMP_EXCEPTION_INFORMATION mei;mei.ThreadIdGetCurrentThreadId();mei.ExceptionPointersex_info;mei.ClientPointersFALSE;MiniDumpWriteDump(GetCurrentProcess(),GetCurrentProcessId(),dump_file,MiniDumpNormal,mei,nullptr,nullptr);CloseHandle(dump_file);}returnEXCEPTION_EXECUTE_HANDLER;// 让系统默认处理继续}// 在main中注册SetUnhandledExceptionFilter(unhandled_exception_filter);#endif在Linux上可以在信号处理函数中使用backtrace()glibc特有和backtrace_symbols()输出调用栈。但这受限于几个条件程序必须用-rdynamic--export-dynamic编译以暴露符号表这些函数本身不在POSIX异步信号安全列表中尽管实践中在崩溃场景下的使用被广泛接受。策略三进程外崩溃监控最健壮的策略是让一个监控进程/线程来检测和报告崩溃。监控进程通过waitpidPOSIX或WaitForSingleObjectWindows等待主进程退出检查退出状态收集core dump或minidump。这种方式的优势是崩溃报告代码不在崩溃的进程内——它不受损坏的堆栈或堆的影响。std::set_terminate与C异常C标准提供了std::set_terminate()来设置当异常未被捕获或noexcept函数抛出异常时调用的回调。这个回调在所有平台上的行为是统一的——但和信号处理函数一样它也被限制在一个非常受限的上下文中执行terminate_handler的标准行为是调用std::abort()。#includeexception#includeiostreamvoidmy_terminate_handler(){// 这里可以安全地使用C I/O不同于信号处理函数std::cerrUnhandled exception, program terminatingstd::endl;// 尝试获取当前异常信息if(autoeptrstd::current_exception()){try{std::rethrow_exception(eptr);}catch(conststd::exceptione){std::cerrException: e.what()std::endl;}catch(...){std::cerrUnknown exception typestd::endl;}}// 仍然需要abortstd::abort();}intmain(){std::set_terminate(my_terminate_handler);// ...}与信号处理函数的关键不同terminate_handler中可以安全使用C标准库std::cerr、std::string等因为此时进程处于正常的执行上下文中未被异步中断。实际建议区分可恢复和不可恢复的异常。SIGSEGV通常不可恢复内存已损坏SIGPIPE应该被忽略或处理正常的网络断开SIGTERM应该触发优雅关闭。在所有平台上测试崩溃处理。不同操作系统、不同编译器版本对信号/异常的处理行为可能有细微差异。在CI中运行sanitizer测试。AddressSanitizerASan和UndefinedBehaviorSanitizerUBSan能在开发阶段尽早发现内存越界、use-after-free等问题而不是等到用户现场崩溃。方案一优先对于需要崩溃报告的生产项目使用CrashpadWindows/macOS/Linux均支持而不是自行实现。崩溃收集的数据量、正确性和跨平台一致性都是经过大量验证的。保留符号文件发布给用户的二进制文件应该strip符号但构建服务器上需要保留带符号的版本用于事后对Crash dump的堆栈回溯进行符号化。