临时文件的使用:tmpfile、tmpnam函数与安全注意事项

临时文件,说白了就是程序运行过程中产生的“一次性”文件。你想想看,有时候我们需要暂存一些中间数据,但又不希望这些文件长期占用磁盘空间——比如编译器生成的临时目标文件、下载工具的分片缓存、或者数据处理时的排序中间结果。这时候,临时文件就派上用场了。

C标准库给我们提供了两个处理临时文件的函数:tmpfiletmpnam。嗯,这里要注意,它们虽然都能生成临时文件,但使用方式和安全性差别很大。我个人习惯是优先用tmpfile,原因后面会讲。

tmpfile函数:自动创建,自动销毁

tmpfile的用法非常直接——它创建一个临时文件,并以二进制读写模式("wb+")打开。最关键的是:这个文件在程序正常结束时会自动删除。你不需要手动调用remove,也不用担心忘记清理。

#include <stdio.h>

int main() {
    FILE *fp = tmpfile();
    if (fp == NULL) {
        perror("tmpfile 创建失败");
        return 1;
    }

    // 写入一些数据
    int data[] = {10, 20, 30, 40, 50};
    fwrite(data, sizeof(int), 5, fp);

    // 重置文件位置指针
    rewind(fp);

    // 读取并打印
    int val;
    while (fread(&val, sizeof(int), 1, fp) == 1) {
        printf("%d ", val);
    }
    printf("\n");

    // 程序结束,临时文件自动删除
    return 0;
}

我在项目中遇到过一个问题:某个数据处理模块需要频繁创建临时文件,如果用普通文件,就得自己写清理逻辑,一旦程序异常退出,磁盘上就会留下一堆垃圾文件。改用tmpfile之后,这个问题就解决了——文件句柄关闭或程序退出时,系统自动清理。

小提示: tmpfile 返回的文件流是以二进制模式打开的。如果你需要文本模式,可以自己用 fopen 创建临时文件,但记得手动删除。

tmpnam函数:生成文件名,但别乱用

tmpnam的作用是生成一个唯一的临时文件名。它不会创建文件,只是给你一个名字。你需要自己用fopen去创建并打开这个文件。

#include <stdio.h>

int main() {
    char filename[L_tmpnam];
    if (tmpnam(filename) == NULL) {
        fprintf(stderr, "无法生成临时文件名\n");
        return 1;
    }

    printf("生成的临时文件名: %s\n", filename);

    // 自己创建文件
    FILE *fp = fopen(filename, "w+");
    if (fp == NULL) {
        perror("fopen 失败");
        return 1;
    }

    fprintf(fp, "这是一些临时数据\n");
    fclose(fp);

    // 记得手动删除
    remove(filename);
    return 0;
}

为什么我说“别乱用”?因为tmpnam存在一个经典的安全问题:TOCTOU(Time of Check, Time of Use)。什么意思呢?你调用tmpnam拿到一个文件名,然后去创建文件——在这两个操作之间,可能有恶意程序用同样的名字创建了一个符号链接,指向系统关键文件。你再去写文件,就写到别人的地盘上了。

安全警告: 永远不要在多线程或多进程环境中使用 tmpnam。TOCTOU 漏洞是真实存在的安全风险。我曾经在一个文件上传服务中看到过类似的漏洞——攻击者通过预测临时文件名,成功覆盖了系统配置文件。

tmpnam_s:安全版本,但有限制

C11标准引入了tmpnam_s作为安全替代。它增加了缓冲区大小检查,但本质上仍然存在TOCTOU问题。所以,我个人建议:能用tmpfile就别用tmpnam系列

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>

int main() {
    char filename[L_tmpnam_s];
    errno_t err = tmpnam_s(filename, L_tmpnam_s);
    if (err != 0) {
        fprintf(stderr, "tmpnam_s 失败\n");
        return 1;
    }

    printf("安全生成的临时文件名: %s\n", filename);
    // 但 TOCTOU 问题依然存在
    return 0;
}

mkstemp:Linux/Unix下的更优选择

如果你在Linux或Unix环境下开发,我强烈推荐用mkstemp。它创建并打开一个临时文件,返回文件描述符,而且文件名是原子性生成的——不存在TOCTOU窗口。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    char template[] = "/tmp/myapp_XXXXXX";
    int fd = mkstemp(template);
    if (fd == -1) {
        perror("mkstemp 失败");
        return 1;
    }

    printf("临时文件: %s\n", template);

    // 使用文件描述符
    write(fd, "hello", 5);

    // 关闭并删除
    close(fd);
    unlink(template);
    return 0;
}
核心要点:
  • tmpfile:自动创建、自动删除、安全,优先使用
  • tmpnam:仅生成文件名,存在TOCTOU漏洞,避免使用
  • tmpnam_s:安全版本但TOCTOU问题仍在,不推荐
  • mkstemp:Linux下推荐,原子操作,安全可靠

临时文件的安全注意事项总结

函数 自动创建 自动删除 TOCTOU风险 推荐度
tmpfile ⭐⭐⭐⭐⭐
tmpnam
tmpnam_s ⭐⭐
mkstemp ⭐⭐⭐⭐

嗯,最后再啰嗦一句:临时文件的路径也很重要。默认情况下,tmpfiletmpnam会把文件创建在系统临时目录(Linux下是/tmp,Windows下是%TEMP%)。如果你需要指定目录,可以用mkstemp自己控制模板路径。我曾经在一个嵌入式项目里,因为/tmp空间不足导致临时文件创建失败,后来改成在数据分区下创建临时目录才解决。

说白了,临时文件虽小,但用不好也会翻车。记住一条原则:让系统帮你管理生命周期,别自己手写清理逻辑

临时文件使用决策流程图 需要临时文件? 你在什么平台上开发? Linux / Unix 跨平台 / Windows 嵌入式 / 特殊环境 推荐:mkstemp 原子操作,安全可靠 推荐:tmpfile 自动清理,简单安全 谨慎:tmpnam 注意TOCTOU风险 核心原则:让系统管理生命周期,避免手动清理
公众号:蓝海资料掘金营,微信deep3321