崩溃本质是程序向只读内存页写入触发段错误;常见于多线程下修改字符串字面量、static const对象或滥用const_cast,需用gdb+info proc mappings、readelf、-wwrite-strings等定位和预防。

崩溃时看到 EXCEPTION_ACCESS_VIOLATION 或 Segmentation fault (core dumped) 怎么定位
这类崩溃本质是程序试图向只读内存页写入,操作系统直接终止进程。C++多线程下特别容易在不经意间触发——比如多个线程同时修改一个被 const 修饰但未真正隔离的全局对象,或通过 const_cast 破坏 const 正确性后并发写入。
关键不是“有没有 const”,而是“内存页是否真的被标记为只读”。编译器可能把字符串字面量、static const 基础类型等放进 .rodata 段,此时哪怕没开 -fPIE 或 -Wwrite-strings,运行时写入也会立即崩。
- 用
gdb ./a.out core启动后执行bt,看崩溃栈顶是否落在std::mutex::lock、std::atomic操作之后——这说明锁没起作用,问题出在保护对象本身不可写 - 在 gdb 中执行
info proc mappings(Linux)或vmmap(macOS)确认崩溃地址是否落在[r--p]区域 - 用
readelf -S ./a.out | grep rodata查看只读段范围,再比对崩溃地址
const char* 被多线程函数意外修改的典型场景
最常见的是把字符串字面量传给一个看似“只读”实则内部会修改缓冲区的函数,比如某些旧版 strtok、自定义解析器、或误用 const_cast<char>(s.c_str())</char> 后传给 C 接口。
多线程放大问题:线程 A 调用 strtok(const_char_ptr, delim),线程 B 同时调用同一函数——strtok 内部用静态指针记录位置,且它会尝试写入传入的字符串首地址(而字面量在 .rodata),两个线程争抢导致崩点飘忽不定。
- 永远不要把
"hello"这类字面量传给任何可能修改内容的函数;改用std::vector<char> buf{"hello"};</char>或std::string s = "hello";再传&s[0] - 检查所有 C 风格字符串处理函数(
strtok,strtol的endptr若指向字面量、strftime输出到字面量数组等) - 启用编译器警告:
-Wwrite-strings(GCC/Clang)会让char* p = "abc"报错,强制你面对这个问题
用 const_cast 绕过 const 后多线程写入为什么更危险
const_cast 本身不改变内存页属性,但它绕过了编译器对 const 对象的写保护检查。如果原对象实际位于只读段(如 static const int x = 42;),那么所有线程通过 const_cast 得到的指针,写入时都会触发段错误——而且无法用互斥锁缓解,因为锁解决不了硬件级只读限制。
- 检查所有
const_cast使用点,尤其注意是否用于全局const变量、模板参数推导出的const T&、或 lambda 捕获的 const 值 - 若必须修改逻辑,优先重构为非 const 对象 +
std::atomic或std::mutex保护;不要依赖const_cast“临时解绑” - LLVM 的
-fsanitize=const-overflow(尚未稳定)或自定义 AddressSanitizer hook 可捕获部分 const_cast 写行为,但不如静态规避可靠
如何用工具提前发现只读内存写风险
运行时检测靠 gdb 和 core dump 是被动手段。更有效的是在构建阶段卡住问题源头。
- Linux 下用
objdump -s -j .rodata ./a.out手动检查高危符号是否意外出现在只读段(比如本该动态分配的 buffer 被优化进 .rodata) - Clang/GCC 加
-frecord-gcc-switches+ 自写脚本扫描编译日志中是否有const char*被赋值给非 constchar*的隐式转换 - 在 CI 中跑
valgrind --tool=memcheck --read-var-info=yes ./test,它能报告对只读段的非法写访问(需编译带 debug info) - 对于 C++20 项目,考虑用
std::string_view替代裸const char*,它明确禁止隐式转为可写指针,编译期就拦住大部分误用
真正麻烦的不是崩溃本身,而是只读内存写在单线程下可能“恰好不崩”(比如写入未映射的页触发 SIGSEGV,但某些布局下碰巧落在可写页),一上多线程就暴露——所以别信“本地跑得通”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











