12、临时文件操作:tmpfile函数、tmpnam函数、临时文件的创建与使用

做嵌入式开发这些年,我遇到过不少需要临时文件的场景。比如日志系统要暂存中间数据,或者某个模块需要交换大块内存放不下的信息。这时候,临时文件就是我们的好帮手。

说白了,临时文件就是“用完即弃”的文件。程序运行期间创建,退出时自动清理。C标准库提供了两个核心函数:tmpfiletmpnam。它们各有各的脾气,用不对会踩坑。

12.1 tmpfile函数:最安全的临时文件

tmpfile是我个人最常用的临时文件函数。它直接创建一个临时文件,返回一个FILE*指针。文件以"wb+"模式打开,可读可写。

它的最大优点是:文件在关闭或程序退出时自动删除。你不需要手动调用remove,系统帮你擦屁股。

核心特点:

  • 自动生成唯一文件名,不会冲突
  • 以二进制读写模式打开("wb+")
  • 关闭文件或程序退出时自动删除
  • 返回NULL表示创建失败

来看一个实际例子。我在做传感器数据采集时,经常需要暂存一批数据再统一处理:

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

int main() {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile failed");
        return -1;
    }

    // 写入一些临时数据
    int data[] = {23, 45, 67, 89, 12};
    fwrite(data, sizeof(int), 5, tmp);

    // 回到文件开头
    rewind(tmp);

    // 读出来验证
    int buf[5];
    fread(buf, sizeof(int), 5, tmp);
    for (int i = 0; i < 5; i++) {
        printf("%d ", buf[i]);
    }
    printf("\n");

    // 关闭时自动删除
    fclose(tmp);
    return 0;
}

嗯,这里要注意:tmpfile创建的文件在磁盘上是有实体的,但你看不到它的名字。系统给它起了个随机名,存在临时目录里。你关掉文件指针,它就消失了。

我曾经踩过的坑:

在某个RTOS环境下,tmpfile返回了NULL。查了半天,发现是系统的临时目录空间满了。所以调用tmpfile后一定要检查返回值,别想当然认为一定成功。

12.2 tmpnam函数:只给你名字,不帮你干活

tmpnamtmpfile不一样。它只生成一个唯一的文件名,不创建文件。你需要自己用fopen去打开它。

函数原型很简单:

char *tmpnam(char *s);

如果s不为NULL,文件名会复制到s指向的缓冲区(至少L_tmpnam个字节)。如果s为NULL,返回一个内部静态缓冲区的指针。

我的建议:

尽量用tmpfile而不是tmpnam。因为tmpnam只生成名字,从生成到打开之间有个时间窗口,别的进程可能用同样的名字创建了文件——这就是所谓的TOCTOU(Time of Check to Time of Use)问题。

但有些场景下,你确实需要自己控制文件名。比如你要把临时文件传给另一个模块,或者需要指定文件路径。这时候tmpnam就派上用场了:

#include <stdio.h>

int main() {
    char filename[L_tmpnam];
    if (tmpnam(filename) == NULL) {
        fprintf(stderr, "tmpnam failed\n");
        return -1;
    }

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

    // 自己创建并打开
    FILE *fp = fopen(filename, "wb+");
    if (fp == NULL) {
        perror("fopen failed");
        return -1;
    }

    // 使用文件...
    fprintf(fp, "临时数据\n");
    fclose(fp);

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

你想想看,tmpnam生成的名称一般长这样:\s123.1或者fileXXXX。不同系统格式不同,但保证唯一。

12.3 临时文件的创建与使用:实战经验

在实际项目中,我总结了一套临时文件的使用套路。这里画个流程图,帮你理清思路:

临时文件使用流程 开始 需要控制文件名? (传给其他模块/指定路径) 使用 tmpnam 生成唯一文件名 使用 tmpfile 自动创建+自动删除 检查返回值是否为NULL 读写数据 / 处理业务 关闭文件 / 手动删除(tmpnam)

流程其实不复杂。核心就两点:要不要控制文件名,以及记不记得清理

12.4 避坑指南:我踩过的那些雷

做嵌入式开发十几年,临时文件这块我栽过好几次。分享几个典型问题:

问题1:tmpnam的缓冲区大小

我曾经在代码里写死了缓冲区为256字节,觉得肯定够用。结果在某个Unix系统上,临时目录路径特别长,文件名加上路径就超了。后来我改用L_tmpnam宏定义的大小,再也没出过问题。

问题2:忘记手动删除

tmpnam创建的文件,程序退出后不会自动删除。我有一次在服务器上跑了个监控程序,每天生成几百个临时文件。一个月后磁盘满了,系统直接挂了。从那以后,我养成了习惯:用tmpnam必配remove,而且放在atexit里注册

问题3:多线程安全

tmpnam如果传NULL,返回的是内部静态缓冲区。多线程环境下,这个缓冲区会被覆盖。我建议:多线程程序一律用tmpfile,或者给tmpnam传自己的缓冲区

12.5 实际项目中的最佳实践

说了这么多,总结一下我个人在项目中的做法:

场景 推荐函数 理由
简单暂存数据,用完就扔 tmpfile 自动删除,省心安全
需要把文件名传给子进程 tmpnam + fopen 可以拿到文件名,但记得手动删
多线程环境 tmpfile 避免静态缓冲区竞争
嵌入式裸机/RTOS 看系统支持 有些RTOS没有标准C库,得自己实现

最后说一句:临时文件虽好,但别滥用。能用内存解决的问题,尽量别碰磁盘。嵌入式设备Flash寿命有限,频繁写临时文件会加速老化。我一般只在数据量超过内存容量,或者需要跨进程传递数据时才用临时文件。

好了,临时文件这块就聊到这儿。记住核心原则:tmpfile省心但不可控,tmpnam灵活但麻烦。根据场景选对工具,比什么都重要。


公众号:蓝海资料掘金营,微信deep3321