C语言-文件操作-一些我想到的、见到的奇怪的问题

embedded/2024/10/21 4:03:07/

博客主页:【夜泉_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类型的结构体并不是通过 malloccalloc 分配的内存,所以使用 free 会导致未定义行为,如,在下一次读取时输出一堆乱码。

7.3意义

虽然 FILE* 在堆上分配,但由于它是由C标准库通过 fopen() 处理的,不应直接使用 free(),而应该使用 fclose() 来正确释放资源!!!


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!


http://www.ppmy.cn/embedded/118274.html

相关文章

2、electron vue3 怎么创建子窗口,并给子窗口路由传参

接上回初始化vue3 electron项目&#xff0c;创建完vue3 electron项目后&#xff0c;现在要实现在渲染进程中点击按钮创建一个新的子窗口 开始 子窗口创建操作只能在主线程内完成&#xff0c;而创建操作是在渲染线程触发&#xff0c;因此就需要进行两者间的通讯。 1、创建子窗…

机器人速度雅可比矩阵求解(2自由度平面关节机器人)

关节速度和末端速度空间的映射需要计算雅可比矩阵的逆矩阵,在博途PLC里如何计算一个方阵的逆矩阵,大家可以参考下面这篇文章: 博途PLC矩阵求逆 矩阵求逆 博图SCL_博图矩阵运算-CSDN博客文章浏览阅读839次。本文介绍如何用C语言实现矩阵求逆的过程,详细解析了相关代码,适…

《MATLAB项目实战》,专栏目录和介绍

文章目录 前言专栏介绍&#x1f393;一、 项目实战篇和GUI界面篇&#x1f393;二、 项目基础篇总结 前言 MATLAB 是一款强大且广泛应用的数值计算和数据可视化软件工具&#xff0c;它提供了一个高效、简洁的编程环境&#xff0c;使用户能够进行从简单的矩阵运算到复杂的多维数…

SVM原理

SVM 这里由于过了很长时间 博主当时因为兴趣了解了下 博主现在把以前的知识放到博客上 作为以前的学习的一个结束 这些东西来自其他资料上 小伙伴看不懂英文的自行去翻译下吧 博主就偷个懒了 多维空间和低维空间 不一样的分法&#xff0c;将数据映射到高维 &…

微信小程序教程:如何在个人中心实现头像贴纸功能

在微信小程序中&#xff0c;个性化设置是提升用户体验的重要手段。本文将详细介绍如何在个人中心模块中实现头像贴纸功能&#xff0c;让用户可以自由地装饰自己的头像。 头像贴纸功能允许用户在个人头像上添加装饰性贴纸&#xff0c;增加个性化表达。以下是实现该功能的主要步骤…

0.设计模式总览——设计模式入门系列

在现代软件开发中&#xff0c;设计模式为我们提供了优秀的解决方案&#xff0c;帮助我们更好地组织代码和架构。本系列专栏将对设计模式的基本思想、原则&#xff0c;以及常用的分类、实现方式&#xff0c;案例对比、以及使用建议&#xff0c;旨在提高开发者对设计模式的理解和…

数据结构——数组

数组的概念 数组&#xff1a;按一定格式排列起来的&#xff0c;具有相同类型的数据元素的集合。 一维数组 一维数组&#xff1a;若线性表中的数据元素为非结构的简单元素&#xff0c;则称为一维数组。 一维数组的逻辑结构&#xff1a;线性结构。定长的线性表。 声明格式&a…

【Webpack】实现持久化缓存

回答重点 在 Webpack 中实现持久化缓存有几个关键策略&#xff0c;最核心的就是利用文件内容哈希&#xff0c;使得文件名发生变化&#xff0c;这样浏览器就会识别为新的资源而不是使用缓存的旧资源。具体步骤如下&#xff1a; 1&#xff09;使用 output.filename 和 output.c…