std::ofstream 无法捕获扇区损坏异常,因其仅封装 POSIX write() 返回值,内核介质错误时返回 -1 并设 errno 为 EIO/ENOSPC,但 ofstream 默认仅置 failbit 而不抛异常。

std::ofstream 写入时根本捕获不到扇区损坏异常
磁盘物理损坏(比如坏扇区)触发的是操作系统内核级 I/O 错误,C++ 标准库的 std::ofstream 完全不感知——它只封装了 POSIX write() 的返回值抽象,而内核在遇到不可恢复的介质错误时,往往直接返回 -1 并置 errno 为 EIO 或 ENOSPC(取决于具体阶段),但 std::ofstream 默认把这类失败静默转为 failbit,不会抛异常,更不会暴露原始 errno。
常见错误现象:ofs 看似成功,<code>ofs.good() 仍返回 true,但实际数据没落盘;后续调用 ofs.flush() 或 ofs.close() 才首次暴露失败,此时已错过上下文。
- 必须显式检查每个写入后的流状态,不能只依赖
operator 返回值 -
std::ofstream构造时不带std::ios_base::exceptions(),默认不抛异常 - 即使设置了
exceptions(std::ios_base::failbit | std::ios_base::badbit),也只对标准库可识别的逻辑错误(如格式化失败)有效,badbit触发条件是底层streambuf操作失败(如sputn返回负值),但部分 libc 实现对EIO处理不一致
用 fsync + errno 检查绕过标准库盲区
真正能捕获扇区级 I/O 异常的,是系统调用层面的同步操作。写完后必须调用 fsync()(Linux/macOS)或 FlushFileBuffers()(Windows),并检查其返回值和 errno / GetLastError()。
使用场景:金融交易日志、数据库 WAL、任何要求“数据真实落盘”的关键路径。
立即学习“C++免费学习笔记(深入)”;
- 先调用
ofs.flush()确保用户缓冲区清空到内核页缓存 - 再用
fileno(offs.rdbuf()->pubsync())获取底层文件描述符(注意:需确保rdbuf()是std::filebuf*,且未被自定义替换) - 调用
fsync(fd),若返回 -1,则立即检查errno——EIO表示 I/O 设备错误(含坏扇区),ENOSPC表示空间不足,EROFS表示只读文件系统 - Windows 下需用
_get_osfhandle(_fileno(ofs))转 HANDLE,再调FlushFileBuffers()
为什么 mmap + msync 不适合检测扇区损坏
mmap() 把文件映射到内存,写操作变成内存赋值,真正的磁盘 I/O 延迟到 msync() 或内核回写时机。但 msync() 在遇到坏扇区时行为不稳定:某些内核版本会返回 -1 + errno = EIO,更多情况是直接触发 SIGBUS 信号(因访问了无法读写的物理页),而 C++ 默认不处理该信号,进程直接终止。
参数差异:msync(addr, len, MS_SYNC) 的 MS_SYNC 保证写回完成,但无法让错误“优雅上报”;MS_ASYNC 更不可靠,连错误都不报。
-
std::ofstream+fsync()路径可控、错误明确、可重试 -
mmap方案需要注册SIGBUS信号处理器,且信号上下文里不能安全调用std::cout或new,工程成本高 - SSD/Fusion Drive 等设备可能把坏扇区映射隐藏,
fsync()仍是目前最通用的检测锚点
一个最小可验证的健壮写入模式
下面这段代码绕过了标准库的异常盲区,直击内核错误码:
#include <fstream>
#include <unistd.h>
#include <cerrno>
#include <system_error>
<p>bool safe_write(const char* path, const std::string& data) {
std::ofstream ofs(path, std::ios::binary);
if (!ofs.is_open()) return false;
ofs.write(data.data(), data.size());
if (!ofs.good()) return false;
ofs.flush();
if (!ofs.good()) return false;</p><pre class='brush:php;toolbar:false;'>int fd = fileno(ofs.rdbuf()->pubsync()); // 注意:pubsync() 在 libstdc++ 中返回 void,此处需用 glibc 的非标扩展或改用 fdopen
// 实际项目中建议用 open()/write()/fsync() 绕过 std::ofstream,更可控
if (fsync(fd) == -1) {
if (errno == EIO) {
// 这里才是真正捕获到扇区损坏
return false;
}
// 其他 errno 按需处理
}
return true;}
容易踩的坑:fileno() 对 std::ofstream 的 rdbuf() 并非完全可移植;libstdc++ 和 libc++ 实现细节不同;生产环境应直接用 POSIX open()/write()/fsync() 链路,避免标准库缓冲层干扰错误传播路径。
扇区损坏不是“会不会发生”的问题,而是“什么时候暴露”的问题——所有依赖 close() 自动刷盘的逻辑,都可能把坏扇区错误掩盖数秒甚至数分钟,直到缓存淘汰或系统负载升高才爆发。



















