博客主页:【夜泉_ly】
本文专栏:【C语言】
欢迎点赞?收藏⭐关注❤️
C语言-文件操作-一些我想到的、见到的奇怪的问题
前言1.在不关闭文件的情况下,连续多次调用 fopen() 打开同一个文件,会发生什么?1.1过程1.2结论1.3意义 2.fseek如果设置到文件前的位置会发生什么?2.1过程1.2结论 3.fseek设置到单个汉字的中间会发生什么?3.1过程3.2结论3.3意义 4.同时读、写同一个文件会发生什么?4.1过程4.2结论 5.在追加模式下使用fseek会不会覆盖原文件?5.1过程5.2结论 6.在只读模式下写入会发生什么?6.1过程6.2结论 7.FILE类型的结构体是开在堆上的吗?如果是,free它一下会发生什么?7.1过程7.2结论7.3意义
前言
关于C语言文件操作的文章在CSND上很多很多,我自身才疏学浅,补充不了什么内容,因此,我决定换一个角度,分享一下我想到的、见到的奇怪的问题。
这个奇怪当然也是我单方面认为的
注:本篇只讨论数据文件,且只讨论纯文本文件(.txt
)。
1.在不关闭文件的情况下,连续多次调用 fopen() 打开同一个文件,会发生什么?
1.1过程
为了验证这个问题,我当然不想创建很多的文件指针来一对一,因此,我尝试使用同名指针两次打开同一个文件:
#include <stdio.h>int main(){ FILE* pf = fopen("test.txt", "w"); printf("time 1:%p\n", pf); pf = fopen("test.txt", "w"); printf("time 2:%p\n", pf); return 0;}
运行结果如下图:
这说明,即便是同一个文件指针,同一个文件,只要使用fopen
,系统都会再用一个新的文件描述符。
那么要验证这个问题就很简单了,代码如下:
#include <stdio.h>int main(){ FILE* pf; int count = 1; while (1) { pf = fopen("test.txt", "w"); if (pf == NULL) { printf("time %d::", count); perror(""); break; } printf("time %d:%p\n", count++, pf); } return 0;}
运行结果如下图:
可以看见当打开第510次时,以及没有更多的文件描述符了。
当然,我用的是VS2022,而在其他环境下具体次数可能会改变,但应该还是会有个限制的。
在现代操作系统中,每个进程可以同时打开的文件数是有限的(通常可以通过 ulimit -n
查看限制)。每次调用 fopen()
,操作系统都会为文件分配一个文件描述符,文件描述符是操作系统为进程管理文件资源的句柄。当打开的文件数量超过系统允许的最大数量时,fopen()
将返回 NULL
。
1.2结论
对文件只开不关,会使得系统提供的文件描述符被耗尽,最后fopen
会返回一个空指针。
1.3意义
如果有人只会开文件,不会关文件,那么那个人多半在开文件时,也不会检查文件是否打开成功?。
那么这样的错误,就有可能出现了:
fputs("HaHa", NULL);
具体场景如下:
#include <stdio.h>int main(){ FILE* pf; int count = 1; while (1) { pf = fopen("test.txt", "w"); if (pf == NULL) { printf("time %d::", count); perror(""); break; } printf("time %d:%p\n", count++, pf); } fputs("HaHa", pf);// pf 此时已经为 NULL了 return 0;}
如果运行,程序最终会崩掉:
因此,在使用完文件后,一定要fclose
!
2.fseek如果设置到文件前的位置会发生什么?
2.1过程
有这样的问题是因为有一天我写了这样一个代码:
#include <stdio.h>int main(){FILE* pf = fopen("test.txt", "w");if (!pf)return 1;int ch = 'a';fputc(ch,pf);fseek(pf, -1000000, SEEK_SET);ch = 'a';fputc(ch, pf);fclose(pf);pf = NULL;return 0;}
我在fgetc
之后用了fseek
,并把位置设置到了开始位置的-10000,之后再次使用了fgetc
。
然后程序运行成功了,并且在文件中输入了两个a
:
aa
为什么不是一个a
或者报错?
其实非常简单,因为刚进fseek
就被弹出来了:
#include <stdio.h>int main(){ FILE* pf = fopen("test.txt", "w"); if (!pf)return 1; int ch = 'a'; fputc(ch, pf); printf("fseek前的偏移量:%d\n", ftell(pf)); if (fseek(pf, -1000000, SEEK_SET)) { perror("fseek failed"); } printf("fseek后的偏移量:%d\n", ftell(pf)); ch = 'a'; fputc(ch, pf); fclose(pf); pf = NULL; return 0;}
运行结果如下图:
1.2结论
什么都不会发生,fseek会返回一个非零值,并设置错误码(对应的错误信息就是 Invalid argument
),但偏移量不会改变。
3.fseek设置到单个汉字的中间会发生什么?
3.1过程
众所周知,一个汉字占多个字节,那我用fseek
设置到这多个字节的中间会发生什么呢?
代码如下:
#include <stdio.h>int main(){ FILE* pf = fopen("test.txt", "w"); if (!pf)return 1; fprintf(pf, "今天的日期:20240921"); fclose(pf); pf = fopen("test.txt", "r"); if (!pf)return 1; fseek(pf, 1, SEEK_SET); char ch[100]; fscanf(pf, "%s", ch); printf("%s\n", ch); return 0;}
运行结果如下:
3.2结论
可能会输出乱码。
3.3意义
在C语言中,有很多地方使用汉字会导致未定义行为,因此尽量避免使用汉字。
4.同时读、写同一个文件会发生什么?
4.1过程
代码如下:
#include <stdio.h>int main() { FILE* write = fopen("test.txt", "w"); FILE* read = fopen("test.txt", "r"); if (!read || !write)return 1; fputs("Hello World!", write); char ch[100]; fgets(ch, 100, read); printf("%s\n", ch); fclose(write); write = NULL; fclose(read); read = NULL; return 0;}
这里,我在对文件写入之后立刻读取,最终得到下图的结果:
但如果我在fputs
语句之后,刷新缓冲区,则会正常输出:
fputs("Hello World!", write); fflush(write);
如果我将fclose
提前,也能正常输出:
fputs("Hello World!", write); fclose(write); write = NULL;
4.2结论
可能缓冲区没被刷新,导致读到乱码或不完整的信息。
5.在追加模式下使用fseek会不会覆盖原文件?
5.1过程
先写,再追加,最后读:
#include <stdio.h>int main() { FILE* write = fopen("test.txt", "w"); if (!write)return 1; fputs("Hello World!", write); fclose(write); write = NULL; FILE* add = fopen("test.txt", "a"); if (!add)return 1; fseek(add, -10, SEEK_SET); perror(""); fputs("xxxxxxxxxx", add); fclose(add); add = NULL; FILE* read = fopen("test.txt", "r"); char ch[100]; fgets(ch, 100, read); printf("%s\n", ch); fclose(read); read = NULL; return 0;}
结果如下图:
5.2结论
并不会覆盖原文件,fseek
又是一进去就被弹出来了(rewind
也不行,会设置在追加的起始位置)。
6.在只读模式下写入会发生什么?
6.1过程
我先写入"Hello World!",然后在只读模式下尝试写入信息(还加了一个perror打印错误信息),最后读取文件信息。
代码如下:
#include <stdio.h>int main() { FILE* write = fopen("test.txt", "w"); if (!write)return 1; fputs("Hello World!", write); fclose(write); write = NULL; FILE* pf = fopen("test.txt", "r"); if (!pf)return 1; fprintf(pf, "HaHa"); perror(""); fclose(pf); FILE* read = fopen("test.txt", "r"); char ch[100]; fgets(ch, 100, read); printf("%s\n", ch); fclose(read); read = NULL; return 0;}
运行结果如下图:
错误信息是坏的文件描述符,可能是指我的文件指针用错了吧。
6.2结论
会拒绝写入,原文件信息不会改变。
7.FILE类型的结构体是开在堆上的吗?如果是,free它一下会发生什么?
7.1过程
先简单验证一下VS2022中是不是开在堆上的:
#include <stdio.h>#include <stdlib.h>int main(){int* a = (int*)malloc(sizeof(int));int* b = (int*)malloc(sizeof(int));FILE* pf = fopen("test.txt", "w");printf("%p\n%p\n%p", a, b, pf);return 0;}
运行结果如下图:
可以发现,这三个变量的地址相近,因此,可以认为FILE类型的结构体是开在堆上的。
既然如此,那么free(文件指针)应该是可以运行的:
#include <stdio.h>#include <string.h>int main(){ FILE* write = fopen("test.txt", "w"); if (!write)return 1; fputs("Hello World!", write); fflush(write); free(write); FILE* read = fopen("test.txt", "r"); char ch[100]; fgets(ch, 100, read); printf("%s\n", ch); fclose(read); read = NULL; return 0;}
运行结果如下图:
7.2结论
在VS2022中,FILE类型的结构体是开在堆上的,因此,free(结构体指针)
时不会报错。
但是,FILE类型的结构体并不是通过 malloc
或 calloc
分配的内存,所以使用 free
会导致未定义行为,如,在下一次读取时输出一堆乱码。
7.3意义
虽然 FILE*
在堆上分配,但由于它是由C标准库通过 fopen()
处理的,不应直接使用 free()
,而应该使用 fclose()
来正确释放资源!!!
希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!