`fopen` 这个函数, 它是 C 语言标准库里面最基础, 同时也是最核心的文件操作函数之一。它是被定义在相应的标题文件里的。它的完整函数原型样子是长这样的: `FILE *fopen(const char *, const char *mode);`。

这个函数的主要作用就是用指定的模式把一个文件打开, 然后它会返回一个指向类型为 `FILE` 的结构体的指针。这个指针通常被称为文件流指针。它是后续所有基于流的方式来进行输入和输出操作的前提条件。

这些操作包括像什么读取一块内存的 ``fread`、`, 写入数据的 ``、关闭文件的 ``等等在内的许多功能都是需要它作为入口点来开始执行的。想要真的搞明白 fopen 是怎么回事, 这就不只是会写几行调用代码那么简单了。它还牵扯到很多其他方面的东西。比如说底层那个文件系统的抽象是怎么弄的。

又比如说是怎么搞缓冲机制的。还有编码是不是兼容也得搞清楚。至于错误处理策略是什么, 这也是一个大问题。再者就是不同平台之间行为有啥差异。最后安全编程的那些做法也得知道。总之这就是一个很大的知识体系。

首先, `FILE` 这个概念, 它并不是那种最基本的数据类型, 它是被标准库定义出来一个不透明的结构体, 这个结构体的作用是什么呢, 就是把文件描述符(也就是 file), 还有缓冲区地址, 以及缓冲区大小, 当前的读写位置, 错误标志(``), EOF 标志(`feof`), 缓冲模式(这里分全缓冲、行缓冲、无这三种情况), 编码状态(尤其在处理宽字符或者 UTF-8 的环境下的时候), 锁状态(这在多线程的场景里很关键)等等这些非常关键的运行时信息, 全都给封装起来了。

程序员是不能直接去访问它所包含的那些内部成员的, 一定要通过标准库那边所提供的接口来进行操作和交互, 这个做法体现出来的是 C 语言在“抽象与实现分离”这上面的设计哲学。

其次呢` const char * ` 这个参数, 得把那个合法的路径字符串给传过去, 它是支持那种绝对路径的, 比如说在系统下面可以写如`"C:\\Users\\Admin\\data.txt" `这样的, 当然它也是支持相对路径的哈, 就像写个如`"./.ini" `或者`"../logs/app.log" `之类的。

fopen函数使用方法_fopen模式参数详解_c语言fopen的路径问题

路径解析是受当前工作目录影响的, 大家可以用 `()` 来获取这个信息。然后也是受操作系统路径分隔符影响的, 一般是用反斜杠 `\`, 在 POSIX 系统中是用正斜杠 `/`, 不过现代的 MSVC、MinGW 都是兼容正斜杠的。

另外还受环境变量扩展的影响, 这个是要手动展开的, `fopen` 本身是不解析 `%PATH%` 或者是 `$HOME` 的。最后是受支持限制影响。

这里有一个地方需要特别注意, 传统的名为 fopen 的函数, 它只能接收窄字符也就是 char 类型的路径名称, 在微软的系统上面, 它没有办法原生地直接打开那些路径名字符串里包含中文以及日文等等 UTF-16 编码格式的文件, 为了解决这些问题, 微软公司推出了一个扩展版本的函数, 这个扩展版本的功能就是来专门处理上述情况的, 这个函数的参数要求输入一个 const 类型的指针地址数据, 这在整个 MSDN 官方技术文档之中, 是被作为平台相关特性来进行重点进行标注说明的内容表现的。

最关键的一点是那个 `const char *mode` 这个参数, 它其实是一个里面包含的字符数量在 1 到 3 个之间的控制字符串, 这个东西主要是用来决定文件的打开方式到底是什么样子的, 还有访问权限是怎样的, 以及创建语义是怎么回事。

标准的模式中, 包括了用双引号加上的小写字母r, 表示只读文本的方式, 这种模式下文件必须已经存在了;接着是用双引号加上的w, 表示只写文本的方式, 如果文件不存在则会去创建它, 如果文件已经存在则会把里面的内容清空掉;还有用双引号加上的a, 表示追加文本的方式, 这时候文件的指针会被置于末尾的位置, 写入数据时不会覆盖原有的内容;之后是用双引号加上的r加一个加号, 也就是r+, 表示读写文本的方式, 同样要求文件必须存在;还有用双引号加上的w加一个加号, 也就是w+, 表示可以读取也可以写入文本, 效果是清空旧文件或新建一个新文件;最后是a+, 表示可以进行读的操作用来追加文本, 这种方式下允许读取文件的全部内容, 但是写入操作强制在文件末尾进行追加。

此外, 还有可能在模式后面加上一串字符, 像那样加上一个双引号里面的 b 这个字母, 以此来标示出二进制模式的用法, 比方比如说 rb、wb+, 这些例子都是成立的, 这种操作是非常非常重要的原因就在于呢文本模式它自己会让换行符做一点改变, 具体就是当写入数据的时候把 n 这个符号换成 r 和 n 这两个符号连在一起, 而当读取数据的时候又会把 r 和 n 这两个符号连在一起的转换回单纯的 n 符号, 但如果你用了二进制模式呢就会把这些乱七八糟的转换统统关掉, 这样就能保证你处理的那些字节数据是一点都不差的, 所以说对于图片这种资源, 音频资料, 包括序列化好的数据, 还有网络协议包之类的不属于普通文字内容的大概都需要用这个二进制模式了。

MSDN 特别强调:在 上混合使用文本/二进制模式可能导致不可预知的读写偏移错误;且 `"a"` 和 `"a+"` 模式下,即使调用 `fseek` 移动读写位置,所有写入操作仍被强制重定向至文件末尾,这是 POSIX 标准明确规定的语义,而非实现缺陷。

在处理错误方面, 当 `fopen` 调用成功的时候, 它会返回一个不等于 NULL 的 `FILE*` 类型的指针, 而如果调用失败的话, 则会返回 NULL, 在这种情况下, 必须马上进行检查 `errno` , 比如遇到 `` 文件不存在、`` 权限不足、`` 进程打开文件数超限 的问题, 同时还要结合使用 `` 或者 `` 来输出诊断的信息, 忽视对返回值的检查是导致程序崩溃或者是静默数据丢失的一个很常见的根源。

MSDN 文档中反反复复地做出警告, 表示没有对 fopen 的返回值进行验证就直接去调用它的话, 将会引发未定义行为 UB, 这有可能触发访问违规或者造成堆栈破坏。

另外, 那个子文件名暗示说这个文档是从 C++ 帮助系统里面出来的, 它属于官方 SDK 的文档体系, 不仅仅涵盖了 ANSI C89/C90 标准所定义的内容, 而且还深层集成了 API 的行为细节, 例如, fopen 在 NTFS 上面支持硬链接以及符号链接解析, 和括号配合起来能够全局设定默认文本模式或者二进制模式, 还有和其他变量协同作用来控制新建文件的权限掩码。

这些扩展出来的特有本事, 在纯 POSIX 这种环境里面(就像 Linux GCC 这样的情况)没法使用, 这就把跨平台开发的时候, 必须去查阅 MSDN 这件事的重要性给凸显出来了。

最后, 我们从工程实践的角度来看。现代的C语言项目应该避免直接地使用 `fopen` 这个函数。我们需要配合使用 `` 来显式地释放资源。这样做是为了防止文件描述符出现泄漏的情况。

我们还要使用 `` 来自定义缓冲策略。这是为了优化程序的性能方面。当代码处于多线程环境之中的时候。我们应该考虑使用 `` 或者 `` 来进行加锁操作。对于那些敏感的文件, 我们要启用对应的功能。

具体是带共享模式参数的这个选项。目的是为了防止并发的时候产生冲突的问题。同时, 我们要优先采用 C11标准里面定义的 ``。这个版本是安全增强版的实现方式。它会返回特定的错误码。而这个错误码就是 `` , 而不是返回 `NULL`。

这样做是为了满足 CERT C编码规范的要求。MSDN帮助文档就是这些最佳实践和底层机理的权威索引源, 它的价值远远超过函数签名本身, 它实际上是C语言系统编程能力的基础性知识图谱。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐