<p>_fopen_s 失败主因是路径编码不匹配(如中文/空格导致乱码)和句柄关闭错配(FILE* 必须用 fclose,非 _close);_fsopen 的 shflag 控制并发访问,磁盘满时需主动检测而非依赖 fopen 返回值。</p>

用 _fopen_s 打开文件失败,常见报错是 errno == 2(No such file or directory)
这不是权限问题,而是路径里有中文、空格或相对路径没按预期解析。Windows 下 _fopen_s 对路径编码零容忍,不支持 UTF-8 字节流传入 —— 它只认当前系统代码页(通常是 GBK 或 ANSI)。如果你从 Qt、std::filesystem::path 或宽字符串转过来的 const char*,大概率已乱码。
- 确保传入的是窄字符串(
const char*),且内容严格符合当前控制台/进程的代码页 - 避免拼接路径:别用
"./data/" + filename这种 C++ string 拼法再转 c_str(),中间可能隐式截断 - 调试时直接打印
strlen(path)和逐字节十六进制值,确认没有 \0 提前截断 - 若必须用 Unicode 路径,改用
CreateFileW+_open_osfhandle,绕过_fopen_s的窄字符限制
_fsopen 的 shflag 参数不是可有可无的开关
它控制的是底层句柄共享模式,直接影响并发读写行为。比如你用 _fsopen("log.txt", "a", _SH_DENYNO),多个线程同时写会丢数据;而用 _SH_DENYRW 又会导致第二个调用直接返回 NULL(errno == 13,Permission denied)。
-
_SH_DENYNO:允许其他进程以任意方式打开该文件 —— 日志场景常用,但需自己加锁 -
_SH_DENYRW:禁止任何其他进程读/写,适合独占配置文件更新 -
_SH_DENYWR:别人能读不能写,适合只读缓存文件被多个模块加载 - 注意:这个标志对已打开的文件句柄无效,只作用于本次打开操作
_fopen_s 和 _fsopen 都不检查磁盘空间,但前者更容易静默失败
当磁盘满时,_fopen_s 在 "w" 模式下仍可能返回成功(errno == 0),直到第一次 fwrite 才崩出 errno == 28(No space left on device)。而 _fsopen 因多了一层句柄控制,在部分 Windows 版本中会更早暴露问题。
- 不要依赖 fopen 类函数的返回值判断“文件就绪”,务必在首次写入后检查
ferror(fp) - 写关键日志前,可用
GetDiskFreeSpaceEx主动预检剩余空间 -
"w"模式慎用:清空文件后立即写失败,会导致数据丢失;建议先写临时文件,再rename替换
混用 fclose 和 _close 会导致句柄泄漏甚至崩溃
_fopen_s 和 _fsopen 返回的是标准 FILE*,必须配 fclose;而 _open / _sopen_s 返回的是 int 类型文件描述符,得用 _close。有人看到名字带 _s 就以为要配 _fclose_s,其实它只是个安全 wrapper,内部还是调 fclose。
立即学习“C++免费学习笔记(深入)”;
- 记住口诀:“
_fopen_s→fclose,_sopen_s→_close” -
_fclose_s是可选的、用于捕获 fclose 失败的函数,不是必须调用的替代品 - FILE* 被 fclose 后再传给
_close,会触发 Windows CRT 断言(Debug 模式下弹窗,Release 下 UB)
事情说清了就结束。最常被忽略的是路径编码和句柄关闭配对 —— 这俩问题在线上环境极难复现,但一出就是偶发 core dump 或数据损坏。



















