tmpfile() 创建的文件没有路径名,其背后是不关联文件系统路径的匿名 inode。它适用于临时缓冲且无需路径的场景,但不支持 mmap、跨进程共享或权限设置;fclose() 后文件立即消失,因底层使用 unlink() 或 O_TMPFILE 使 inode 引用归零。

tmpfile() 创建的文件到底有没有路径名
tmpfile() 返回的是一个打开的 FILE*,背后对应内核中真实存在的匿名 inode,但**不关联任何文件系统路径**。这意味着你无法用 ls、stat 或其他外部工具看到它,也无法通过 open("/proc/self/fd/N") 反向获取路径(除非自己用 readlink("/proc/self/fd/N") 查,但返回的是 (deleted))。
常见错误是试图对 tmpfile() 的返回值调用 fileno() 后再传给 mkstemp() 风格的路径操作——没意义,因为根本没路径。
- 适用场景:需要临时缓冲、中间数据暂存,且不希望文件残留或被其他进程猜到路径
- 不适用场景:需要 mmap、需要跨进程共享、需要显式设置权限或扩展属性
- 注意:
tmpfile()在大多数 libc 实现中会自动加O_CLOEXEC,但别依赖——fork 后子进程若未显式fclose(),文件描述符仍会继承(只是父进程 fclose 后子进程还能读写)
为什么 fclose() 后文件就立刻消失,而不是等进程退出
因为 tmpfile() 底层通常等价于先 open("/tmp", O_TMPFILE | O_RDWR)(Linux 3.11+),或 mkstemp() + unlink() 组合。关键在 unlink() 是立即执行的:文件 inode 引用计数归零时内核直接回收空间。
所以只要调用 fclose(),底层 fd 关闭,inode 引用只剩 FILE* 自身;而 fclose() 内部会 close fd 并释放结构体,引用计数清零,文件内容立刻不可访问。
立即学习“C++免费学习笔记(深入)”;
- 陷阱:如果用
dup(fileno(fp))复制了 fd,只fclose(fp)不够,必须也close()那个 dup 出来的 fd,否则文件不会释放 - 验证方法:在
fclose()前用lsof -p $PID | grep deleted能看到该 fd 对应 (deleted) 条目 - Windows 上行为不同:
tmpfile()实际会创建带随机名的真实文件,并在fclose()时unlink(),但路径可被猜测(不推荐生产环境用)
tmpfile() 和 tmpfile_s() 的安全差异在哪
tmpfile_s() 是 C11 Annex K 的“安全函数”,要求传入一个 errno_t* 输出参数,并在失败时设 *errnum = errno。但它**不比 tmpfile() 更安全**:两者都可能因磁盘满、权限不足、ulimit 限制失败,且 tmpfile_s() 在多数 Linux libc(如 glibc)里压根没实现,链接时报 undefined reference。
真正影响安全的是调用上下文:
- 不要在 setuid 程序里用
tmpfile()——它默认使用/tmp目录,而该目录通常是全局可写,存在符号链接竞争风险(尽管O_TMPFILE可缓解) - 更稳妥的做法是用
mkstemp()配合secure_getenv("TMPDIR")和手动unlink(),可控性更强 -
tmpfile()不接受自定义模板或目录,无法规避 /tmp 满或 noexec 挂载等问题
用 tmpfile() 写二进制数据要注意什么
默认以文本模式打开(C 标准规定 tmpfile() 等价于 fopen(..., "w+b")),但 Windows 下文本模式会把 \n 转成 \r\n,破坏二进制一致性。
- 务必用
fwrite()/fread(),别用fputs()/fgets() - 写完后用
fflush(fp)确保数据落盘,尤其后续要rewind()或fseek() - 如果需 mmap,得先
fflush()+fileno()+msync(),但注意:O_TMPFILE文件不能 mmap(ENODEV),此时必须换mkstemp() - 错误现象举例:
fwrite(buf, 1, len, fp)后直接rewind(fp)读,却读到全 0——大概率是忘了fflush()导致缓冲区未写入
临时文件管理的核心不是“怎么建”,而是“谁负责删、何时删、删不掉怎么办”。tmpfile() 把删除时机锁死在 fclose(),看似省心,实则掩盖了资源生命周期的复杂性。比如异常分支漏掉 fclose(),或者 longjmp 跳过清理,文件就卡在内核里直到进程退出——这种隐式依赖,比显式 unlink() 更难调试。



















