详解C语言tmpfile与tmpnam:轻松创建与管理临时文件存储!
不知不觉,围绕“输入输出流”主题已经写了14篇了。除了开始的一篇文章是整体介绍C语言的“数据流”思想(把一切I/O操作都看做数据的流动),后面全部都是在讲解“文件流”的各种功能函数。
今天是讲解文件操作的是最后一篇,后面将围绕标准流(stdout、stderr、stdin等)介绍各种输入输出函数,以及格式字符串的详细用法,包括格式字符串中简单的正则表达式,介绍完这些,才能真正结束这期系列讲座。
本期重点介绍如何在程序运行中创建临时文件的方法,以及其他几个重要的文件操作函数,分别是文件删除函数remove、文件改名函数rename、创建临时文件函数tmpfile、创建唯一的临时文件名函数tmpnam等函数。
因为讲解创建临时函数之前,要用到remove函数,所以我们先介绍下文件删除函数和重命名函数。
文件删除函数:remove()
这是一个很实用的文件处理函数,用来删除硬盘上存在的文件。只需要包含stdio.h文件即可使用。函数原型如下:
int remove( const char *fname );
功能:删除参数 fname 所指向的字符串所标识的文件。 成功删除返回值为0,删除失败返回值为非零值。举例如下:
#include <stdio.h>
int main(){
int stat = remove("d:/path/1.txt");
if (!stat) printf("删除成功。\n");
else printf("删除失败。\nn");
文件重命名函数:rename()
rename函数将文件名改成新的文件名,函数原型如下:
int rename(const char *oldname,const char *newname);
oldname : 原文件名,newname :新文件名。改名成功返回0,改名失败返回非零值。
这个函数在使用时,有以下的细节要注意:
- rename函数可以修改文件名
- rename函数可以修改目录名
- rename函数可以移动文件到新的目录下
- rename函数可以移动目录到新的目录下
- 当函数为非零值时,可以用perror函数输出错误原因
下面举例如下:
#include <stdio.h>
int main(void){
printf("修改文件名:\n");
int stat = rename("1.txt", "2.txt");
if (!stat) printf("1.txt 修改成 2.txt。\n");
else perror("err msg:");
printf("修改文件名:\n");
stat = rename("1.txt", "2.txt");
if (!stat) printf("1.txt 再次修改成 2.txt。\n");
else perror("err msg:");
printf("修改目录名:\n");
stat = rename("./firstdir", "./thirddir");
if (!stat) printf("firstdir 目录被修改成 thirddir目录。\n");
else perror("err msg:");
printf("移动文件:\n");
stat = rename("2.txt", "./seconddir/2.txt");
if (!stat) printf("2.txt 移动到seconddir 目录。\n");
else perror("err msg:");
printf("移动目录:\n");
stat = rename("./seconddir", "./thirddir/seconddir");
if (!stat) printf("seconddir 目录移动到thirddir目录下。\n");
else perror("err msg:");
return 0;
}
因为百家号没有代码块功能,为防止排版变形,下面是源码截图:
代码非常简单,分别演示了利用rename函数进行文件改名、目录改名、文件移动、目录移动等情况。程序执行之前当前目录内文件和子目录的情况如下:
红框内为即将被程序处理的文件和目录,当程序执行后,当前目录内文件和目录情况如下:
程序执行效果吐下:
创建临时文件函数:tmpfile和tmpfile_s
有时候我们需要在程序执行的过程中创建一个文件,用以临时存储数据(类似于磁盘上的缓存文件),并根据需要进行读写,当程序结束运行时,再删除该文件。
按照我们前面学过的知识,会很容易实现,比如:
FILE *fp = fopen(“tmpfile.txt”,”wb+”);
// 对文件进行读写操作......
fclose(fp);
remove(“tmpfile.txt”);
return 0;
我们首先以二进制和写扩展模式打开文件,即fopen或fopen_s等函数打开时要用”wb+”的模式。然后对文件进行读写操作,当文件不再被使用时,首先关闭文件,然后再删除文件即可。
这个功能在程序开发中非常实用和重要,C标准库专门提供了创建临时文件的函数,比如tmpfile()函数、tmpfile_s()函数、tmpnam()函数等等,通过使用这些函数,会非常方便的创建各种用途的临时文件,程序员不用考虑文件创建、关闭和删除等一系列问题。
tmpfile()
tmpfile函数,见名知意,“tmp”就是️temporary,“临时的”英文的简写。temporary按照约定俗成的惯例(计算机领域),一般都简写为tmp或temp。
️花絮:temp和templ的缩写问题
另一个和️temporary相似的单词️template,是C++中的“模版”关键字,和temporary前半部分相同,很多初学者在用这两个单词的缩写时比较随心所欲,不按照规范来。一般情况下,我们都把temporay简写为tmp或temp,把template简写为️tmpl。tnpfile就表示临时文件,tmpfile函数原型如下:
FILE* tmpfile(void);
成功创建临时文件就返回FILE*文件指针,否则返回 NULL 指针。
使用方法如下:
FILE *fp = tmpfile();
//正常读写该文件......
tmpfile以“wb+”模式创建一个临时文件,并返回文件对象指针供调用。当你不再需要对该文件进行操作时,可以不用理会,程序结束时会自动删除。或者你也可以随时用fclose(fp)关闭它,程序会立刻自动帮你删除它。
tmpfile_s()
如果看过我前面系列文章的读者,就知道_s后缀(s表示safe,安全)表示这个函数是tmpfile函数的安全版本,和tmpfile的区别是原本tmpfile的返回值,变成tmpfile_s函数的输出参数,而tmpfile_s返回值变成errno_t类型,用来返回错误原因,当然也做了很多安全检查,初学者可以忽略,我们目前只关注如何使用即可。
函数原型如下:
errno_t tmpfile_s(FILE** fp_ptr);
参数fp_ptr是一个文件对象指针的指针(二级指针)。函数执行成功,则返回 0;如果失败,则为错误代码。
话不多说上例子,如下:
#include <stdio.h>
int main( ){
FILE *fp;
errno_t err = tmpfile_s(&fp);
if( err ) perror( "err msg:" );
//写入数据......
ffprintf(fp,”hello”);
fflush(fp);
//读取数据......
char str[6];
fscanf(fp,”%s”,str);
return 0;
}
当函数执行失败时,perror函数先是原样输出字符串参数,然后会输出错误码的文字性描述。
tmpnam()
当程序运行时,我们有时候需要创建一个临时文件,但有可能会造成重名,因此希望一次创建成功,不会覆盖可能重名的文件,这时需要使用tmpnam函数。
tmpnam函数中tmp是temporay,临时的含义,nam是name,名字的缩写。这个函数不能直接创建临时文件,而是可以创建一个在目录中唯一的“字符串”作为文件名,然后再结合fopen等文件打开函数一起使用
tmpnam函数的原型如下:
char *tmpnam(char *str);
函数的参数和返回值指向的都是同一个字符串地址。若参数不是NULL,则优先使用str作为文件名。否则直接用返回值(返回值为指向内部静态缓冲区的指针)。但是若不及时取出对应的字符串作为文件名使用,该内存地址可能会被覆盖。若不能生成适合的文件名,则返回空指针。
举个例子:
#include <stdio.h>
#include <stdlib.h>
int main(){
char tmpName[L_tmpnam];
if (tmpnam(tmpName) == NULL) {
perror("err msg:");
return -1;
}
printf("%s\n",tmpName);
FILE* fp = fopen(skywy.cn tmpName, "wb+");
fprintf(fp, "hello!");
fflush(fp);
rewind(fp);
char s[10];
s[6] = '\0';
fscanf(fp, "%s", s);
printf("%s\n", s);
fclose(fp);
if (remove(tmpName)) {
perror("remove err:");
return -1;
}
return 0;
}
L_tmpnam是C语言标准库提供的全局常数(或者说符号常量),用来表示tmpnam函数所生成的最大文件名的字符个数,系统默认定义为260,足够用了。
程序首先定义一个能接受L_tmpnam个字符的数组,用来准备接收tmpnam函数创建的临时文件名,然后判断tmpnam函数是否返回NULL,返回NULL说明临时文件名创建失败。
创建成功后,输出创建的临时文件名,然后通过fopen创建文件,文件名就是tmpnam函数创建的。
接着演示了对创建的文件进行写入和读取,最后关闭文件,并删除该临时文件。
tmpnam_s函数
这个函数是tmpnam的安全版本,函数原型如下:
errno_t tmpnam_s(dghzwq.cn char * str,size_t charNum);
相对于tmpnam函数而言,tmpnam_s函数的安全性有了明显的变化。
首先参数charNum表示字符串str的长度,长度的显式指定降低了缓冲区溢出的风险。errno_t类型明确确定了函数执行失败时的原因。
举例如下:
#include <stdio.h>
int main(){
FILE* fp;
errno_t err;
char tmpName[L_tmpnam_s];
err = tmpnam_s(tmpName, L_tmpnam_s);
if (err) {
perror("err msg:");
return -1;
}
err = fopen_s(&fp, tmpName, "wb+");
if (err) {
perror("err msg:");
return -1;
}
//开始读写操作
//......
fclose(fp);
if (remove(m.gushihejiu.cn tmpName)) {
perror("remove err:");
return -1;
}
return 0;
}
当使用tmpnam_s函数时,文件名的最大长度可以用符号常量L_tmpnam_s来表示,L_tmpnam_s和L_tmpnam完全一样,只是在tmpnan函数中用L_tmpnam来表示最大文件名长度,在tmpnam_s函数中用L_tmpnam_s来表示最大文件名长度,以示区别。
tmpnam和tmpfile函数的辨析
既然tmpfile函数能一步到位的实现唯一文件名的临时文件创建并且可以自动删除,为什么C语言标准库还提供了tmpnan函数和fopen结合的用法?
虽然tmpfile 函数会为你处理所有细节,甚至你都不用知道文件名是什么,文件存放在哪。但是,有时候我们需要更精细的控制临时文件创建的行为,比如指定临时文件的存放位置、读写模式、文件格式等等,就只能用tmpnam函数先生成一个唯一的字符串,这样才能控制文件的存放位置,以及读写模式等。
但是,在现代编程实践中,tmpnam 函数的使用已经不是很常见了,因为它不如 tmpfile安全。主要原因如下:
️1)tmpfile 不用考虑文件的删除,可以相对于tmpnam函数减少因忘记删除临时文件而导致的潜在风险。
️2) tmpnam 生成dlhhzs.com.cn的文件名只是静态内存中的一个字符串,因此如果在生成文件名后,没有及时取出来给fopen等函数使用,有可能会导致数据泄露或文件覆盖。而且,tmpnam 在多线程环境中也存在不安全的可能。
TMP_MAX、FOPEN_MAX、FILENAME_MAX
我们介绍了tmpfile、tmpnam函数,以及他们的安全函数,他们的使用非常简单,现在我们来详细介绍下他们在使用中的一些注意事项和需要注意的地方。
1)
tmpfile函数的本质是简化创建一个临时文件的过程。
2)
tmpfile函数等价于用 fopen(或fope_s等函数) 以 "wb+" 模式创建并打开一个二进制文件。稍微不同的是它能保证️该文件名在文件系统中唯一。
3)
tmpnam函数的本质就是创建一个“字符串”,只是这个字符串如果作为新创建的文件的“名称”,在文件系统中不会重名。
4)
在程序的运行时期,临时文件创建的个数理论上不能大于符号常量TMP_MAX所规定的值,TMP_MAX是C语言标准库的符号常量,在VS编译器里被定义为2147483647,gcc编译器是32767。
5)
TMP_MAX的值在程序中被被tmpfile函数、tmpnam函数所共同分配。还有可能受 FOPEN_MAX 所进一步限制。FOPEN_MAX是C语言标准库提供的符号常量,表示在程序中能同时打开的文件数量的最大值,一般为20。所以虽然可以创建非常多的临时文件,但使用完毕,要及时关闭,即使tmpfile创建的临时文件,也可以用fclose函数及时关闭,减少占用的文件打开数量。
6)
C语言标准库提供了一个符号常量FILENAME_MAX,用来约束文件名的最大字符个数,一般为260。在tmpfile和tmpnam函数中,等价于L_tmpnam符号常量,在tmpfile_s和tmpnam_s函数中等价于L_tmpnam_s符号常量。
段誉,2024年4月21日,写于合肥。
一直在纠结,要不要坚持这种费劲又不讨喜的写法?要不要写一点轻松活泼的文章?比如“原来C语言也能玩转数据库?”、“C语言邪能写网页?”等等这种阅读起来技术难度很低文章。
我这种按照C语言标准库来安排知识结构的写法,我自己都怀疑。但是如果真正需要学习C语言的同学,肯定是最好用、最适合的。
还有一个就是最近非常困扰我的就是要不要创作视频?我也曾有过将技术文章做成视频的念头,也做过几篇,而且视频的流量比文章推荐量、阅读量都高的多。但是,我个人还是喜欢觉得学技术,看文章肯定比看视频更合适。难道时代变了,我们也不能抱残守缺了吗?苦恼。