std::ofstream写入失败时badbit不一定被置位,因磁盘满(ENOSPC)等系统错误不自动映射到流状态;必须显式flush()后检查fail()并立即保存errno,跨平台需分别处理ENOSPC和ERROR_DISK_FULL。

std::ofstream 写入失败时 badbit 不一定被置位
很多人以为只要磁盘满了,ofstream 就会自动设置 badbit,结果在代码里只检查 fail() 或 bad() 却漏掉了真实错误。实际上,Linux/macOS 下磁盘满触发的是 errno == ENOSPC,而 C++ 标准流默认**不把系统 errno 映射到 stream state** —— badbit 通常只响应底层 write() 返回 -1 且 errno 非 ENOSPC 的情况(比如文件描述符失效),但写满磁盘时 write() 可能仍返回成功字节数(最后一次短写),或返回 -1 + ENOSPC,此时 ios_base::iostate 可能仍是 goodbit。
实操建议:
- 永远不要只依赖
stream.bad()或stream.fail()判断磁盘满;必须结合errno - 每次调用
write()、put()、operator 后,若操作看似“成功”,但后续flush()或close()失败,问题往往出在缓冲区落盘阶段 - 开启
exceptions()并捕获std::ios_base::failure是更主动的方式,但它仍不会抛出 ENOSPC —— 你得自己查errno
写入后必须显式 flush() + 检查 fail() 才能暴露磁盘满
因为 ofstream 默认带缓冲,数据先写进内存 buffer,等 buffer 满、flush() 或析构时才真正调用系统 write()。磁盘满的错误常卡在这一步,而不是 那一刻。
常见错误现象:os 看似没报错,但程序退出后文件为空或截断 —— 实际是 flush 阶段失败,而你没检查。
实操建议:
- 每次关键写入后调用
os.flush(),然后立刻检查if (os.fail()) { /* 查 errno */ } - 关闭前务必
os.close()并检查返回值(close()成功返回 true,失败返回 false,且会设failbit) - 示例:
std::ofstream os("log.txt");<br>os os.flush();<br>if (os.fail()) {<br> if (errno == ENOSPC) {<br> // 磁盘满<br> }<br>}
跨平台检测 ENOSPC 要注意 errno 重置时机
errno 是线程局部的,但它的值只在系统调用失败时被设置;C++ 流操作本身不保证修改 errno,所以你不能在 os.fail() 为 true 后无条件读 errno —— 中间如果调用了任何可能改 errno 的函数(比如 std::cout ),errno 就可能被覆盖。
使用场景:日志服务、嵌入式设备本地存储、长时间运行的采集程序。
实操建议:
- 在
flush()或close()后**立即**保存errno,例如:int saved_errno = errno; - Windows 下对应错误码是
ERROR_DISK_FULL(206),需用_doserrno或GetLastError(),和 POSIX 的errno不互通 - 别用
std::strerror(errno)直接打印,它不是线程安全的;优先用std::strerror_s(MSVC)或strerror_r(POSIX)
用 RAII 封装写入并统一错误处理比裸用 ofstream 更可靠
手动记 errno、反复 flush、检查每一步,容易漏。尤其在异常路径或提前 return 时,close() 可能根本没执行。
性能影响:RAII 封装本身无额外开销,但强制 flush() 会减少缓冲收益;可按需开关(如仅 debug 模式全 flush)。
实操建议:
- 写一个轻量 wrapper,构造时 open,析构时 close 并检查;提供
commit()方法显式 flush+check - 避免在析构里 throw 异常(违反 C++ 规则),改为记录错误码或调用回调
- 示例核心逻辑:
class SafeWriter {<br> std::ofstream os;<br> int last_errno = 0;<br>public:<br> void commit() {<br> os.flush();<br> if (os.fail()) last_errno = errno;<br> }<br> ~SafeWriter() {<br> os.close();<br> if (os.fail() && !last_errno) last_errno = errno;<br> // 处理 last_errno<br> }<br>};
磁盘满不是流状态机的“标准错误路径”,它是系统资源层的反馈,C++ iostream 层对此保持沉默 —— 这个沉默最容易被当成“没出错”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











