std::ofstream::close()是唯一能捕获物理写入失败的时机,因它内部调用fsync()或flushfilebuffers(),但需显式检查failbit或启用异常才能发现eio等错误。

write() 系统调用返回值不为0 ≠ 写入成功
物理介质损坏(如坏扇区、SSD掉盘、USB设备突然断连)在 C++ 标准文件流(std::ofstream)中**几乎不会触发异常**,哪怕你开了 exceptions(std::ios_base::failbit | std::ios_base::badbit)。因为底层 write() 成功返回字节数后,数据可能还卡在内核 page cache 里,真正落盘时才出错——而那时 C++ 流早已“认为写完了”。常见现象是:程序无报错退出,但文件末尾缺失、内容截断、甚至整个文件为空。
-
std::ofstream::write()返回的是“已接受写入的字节数”,不是“已刷盘字节数” -
std::ofstream::flush()仅把缓冲区推到内核,不保证磁盘提交;它也不会抛异常,即使内核已标记设备为只读或 I/O error -
std::ofstream::close()是唯一可能暴露底层错误的时机,但默认行为是静默丢弃错误
必须显式检查 close() 的失败状态
std::ofstream::close() 内部会调用 fsync()(Linux/macOS)或 FlushFileBuffers()(Windows),这是唯一能捕获物理写入失败的机会。但标准流不提供返回值,必须靠 rdstate() 判断:
std::ofstream ofs("data.bin", std::ios::binary);
ofs.exceptions(std::ios_base::failbit | std::ios_base::badbit);
try {
ofs.write(buf, size);
// ... 其他写入
ofs.close(); // 关键:这里才真正落盘并检测硬件错误
} catch (const std::ios_base::failure& e) {
// 可能捕获 close 失败(如果启用了异常)
} finally {
if (ofs.fail()) {
// 即使没开异常,也要查 failbit —— close 失败会置位 failbit
// 此时 errno 可能为 EIO、ENODEV、ENXIO 等
int err = errno;
std::cerr
- 不要依赖
good()或!fail()在write()后立刻判断——此时毫无意义 - 务必在
close()后立即检查fail()或bad();badbit更倾向底层系统错误,failbit可能含逻辑错误,二者都需处理 - Linux 下可读取
/proc/self/fdinfo/<fd></fd>查看 close 前的write_bytes和read_bytes,但生产环境不推荐
更可靠的方案:绕过 iostream,用 POSIX write + fsync + errno
当需要强一致性保障(如日志、数据库 WAL、金融交易记录),建议跳过 std::ofstream,直接使用系统调用。这样能精确控制错误来源和重试逻辑:
int fd = open("data.bin", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { /* handle open error */ }
<p>ssize_t written = write(fd, buf, size);
if (written != size) {
// write 短写(partial write)本身已是警告信号,尤其配合 EIO
if (written == -1 && errno == EIO) {
// 物理介质错误最典型的 errno
goto cleanup;
}
}</p><p>// 强制刷盘并检测
if (fsync(fd) == -1) {
if (errno == EIO || errno == ENXIO || errno == ENODEV) {
// 这里大概率是磁盘故障
std::cerr </p><p>close(fd); // close 也可能返回 -1 + EIO,但概率低</p>
-
write()返回 -1 且errno == EIO是物理损坏最直接的信号,比流操作早得多 -
fsync()是关键防线:它阻塞直到数据真正写入介质,并将底层错误映射为errno - 注意
O_DIRECT可绕过 page cache,但要求对齐、无缓冲,反而增加出错复杂度,一般不推荐用于通用场景
补充验证:写后立即读回校验(适用于关键小文件)
即使 fsync() 成功,也不能 100% 排除介质静默错误(silent corruption)。对高可靠性场景,可在写入后立即打开文件读回并比对:
- 仅适用于文件不大、性能可接受的场景(如配置文件、证书、种子数据)
- 读回时同样要检查
read()返回值和fsync()后的close()状态 - 校验方式优先用内存 memcmp,避免引入额外哈希计算开销;若文件大,至少校验头部+尾部+固定偏移处的 magic 字段
- 注意:该方法无法发现“写入时就损坏但未报错”的情况(如控制器固件 bug),只能发现“写入后介质自发翻转”
物理层错误检测没有银弹。核心原则是:别信流对象的 good(),紧盯 close() 和 fsync() 的 errno;越靠近系统调用,越早拿到真实错误。硬盘不会告诉你它快坏了,但内核会——只要你记得去问。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











