Section outline

  • <stdio.h> – 输入/输出(Input/Output)


    文件与文件系统(Files and File Systems)

    文件和目录系统的许多方面是实现相关的,甚至连“文件名”这种最基本的概念,标准 C 都无法统一规范。

    • 实现必须支持哪些格式的文件名?

    • 目录和设备名的规则也没有统一标准。

    • 虽然标准头文件名是固定的,但这些名称不一定与文件系统中的实际文件名完全一致

    一些实现可能允许文件名包含通配符,例如 *.dat 表示所有以 .dat 为扩展名的文件。但标准 I/O 函数并不要求支持通配符功能

    操作系统可能会对每个用户限制最大打开文件数。有些文件系统不允许同一目录中存在多个同名文件,这对使用 "w" 模式的 fopen 可能造成影响。

    一些文件系统对用户设置磁盘配额,如果文件过大可能导致写入失败,直到写操作失败时才会察觉

    经研究发现,为实现可移植性,标准 C 认为“安全”的文件名格式是:最多六个字母 + 一个点 + 最多一个字母的扩展名,并建议统一大小写。但你也可以用条件编译来适配不同平台。

    命令行重定向的行为(如 stdin 重定向为文件)是实现相关的。例如,getsfgets(stdin) 行为略有不同,但如果 stdin 被重定向,gets 实际上也可能从文件读取。

    文件缓冲、磁盘扇区大小等也都是实现相关的。但标准 C 要求实现至少能够处理每行包含 254 个字符(包括换行符)的文本文件。

    stdin, stdout, stderr 有时由操作系统维护,有时在程序启动时创建。这三者是否计入最大打开文件数由实现决定。

    下列宏的值为实现定义:

    • BUFSIZ:默认缓冲区大小

    • FOPEN_MAX:允许同时打开的最大文件数

    • FILENAME_MAX:合法文件名最大长度

    • TMP_MAX:可生成的唯一临时文件名最大数

    参见: C11 引入的“边界检查接口”附录可能对该头文件提出扩展要求。

    C++ 注意事项:对应标准 C++ 头文件为 <cstdio>


    文件操作函数(Operations on Files)

    remove 函数

    • 通常会真正删除文件

    • 但如果该文件名只是某个文件的“别名”(例如硬链接),则只有最后一个别名被删除时,文件才会被真正删除

    • 如果删除的文件正在被打开使用,行为是实现定义的

    rename 函数

    • 旧文件名会被删除,类似调用了 remove

    • 如果旧文件正在被打开使用,行为是实现定义的

    • 如果新文件名已存在,行为同样实现定义

    • 在某些文件系统中,不能跨目录重命名,可能会失败,也可能实际是执行复制+删除。

    tmpfile 函数

    • 如果程序异常终止,临时文件可能不会被删除

    • 临时文件的目录、权限、文件名等都是实现定义的

    tmpnam 函数

    • 超过 TMP_MAX 次调用时,行为是实现定义的

    • 若提供的非 NULL 地址指向的缓冲区小于 L_tmpnam 字符,行为未定义

    • 返回的文件名在调用时是唯一的,但在使用前可能已被其他程序创建,若需避免此问题,应使用 tmpfile


    文件访问函数(File Access Functions)

    fclose

    • 程序异常终止时,不能保证输出流会自动刷新写入缓冲区。

    • 在某些实现中,若未写入内容可能无法成功关闭并保留空文件。

    fflush

    • 如果是输入流,或上次操作非输出,则行为未定义。但一些实现允许安全地刷新输入流。

    • stdout, stderr 允许调用 fflush

    • stdin 上的 fflush 是未定义行为,但一些实现允许这么做。

    fopen

    • 一些实现可能在文本文件上无法执行 seek,此时 + 模式可能意味着必须加上 b

    • 'w' 模式下,如果文件已存在,有些系统是覆盖,有些则是创建新版本

    • 模式字符含义是实现定义的。C11 增加了独占模式 'x'

    • 二进制文件关闭时,有些系统会在末尾添加 \0,之后以追加方式打开时可能从文件尾之后开始写入。

    • 只有当实现确定打开的是非交互设备,才使用全缓冲。

    • 成功时返回 FILE*,失败返回 NULL。标准 C 不保证设置 errno

    freopen

    • 成功时返回 stream,失败返回 NULL。是否设置 errno 未定义。

    setbuf

    • 无返回值,程序员需确保传入的是已打开文件流,缓冲区为 NULL 或足够大。

    • 实现可将不同缓冲类型视作等效。即使设置了缓冲方式,也可能无效,且不会有错误提示。

    setvbuf

    • mode 可为:_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(无缓冲)。

    • 返回 0 表示成功,非 0 表示失败(例如 mode 无效),不保证设置 errno

    • 若程序员提供了缓冲区,该缓冲区在流关闭前必须保持有效。

    • 实现可能不会真正使用提供的缓冲区。

    • 缓冲区大小是实现定义的


    格式化输入/输出函数(Formatted Input/Output Functions)

    以下函数的行为,均共享 fprintf / fscanf 所定义的通用格式规则。

    fprintf

    • 若参数不足,行为未定义

    • C89 增加了 i, n, p 转换符(p 以实现定义格式输出 void*)。

    • C99 增加了 F, a, A 转换符,以及 hh, ll, j, t, z 长度修饰符;支持无穷大和 NaN。

    • 无效转换符行为未定义,标准保留未使用的小写符号供将来使用。

    • 若参数是结构体、联合体或数组(除 %s%p 允许的情况),行为未定义

    • 没有原型声明时调用该函数,行为未定义

    • 格式化输出缓存有限,标准要求至少支持单项转换为 509 字符。

    fscanf

    • 输入行为与 fscanf 类似,错误返回 EOF,不保证设置 errno

    • p 要求参数为 void*,格式为实现定义。

    其他格式化函数(所有以下函数在 C99 引入)

    输出类函数(遵循 fprintf 行为):

    • printf, snprintf, sprintf, vfprintf, vprintf, vsnprintf, vsprintf

    输入类函数(遵循 fscanf 行为):

    • scanf, sscanf, vfscanf, vscanf, vsscanf


    字符输入输出函数(Character I/O Functions)

    gets

    • 在 C11 中被移除,因为其存在安全隐患(缓冲区溢出风险)。

    ungetc

    • C99 弃用了在二进制文件开头使用该函数的行为。


    直接输入输出函数(Direct I/O Functions)

    fread

    • 发生错误时,文件位置指示器的值为不确定

    • 若只读取部分字段,该字段值为不确定

    • 标准未规定是否将 CR/LF 对在读取时转换为换行符,某些实现对文本文件会这样处理。

    fwrite

    • 同样,发生错误时,文件位置指示器为不确定

    • 输出时是否将换行符转换为 CR/LF 对也是实现定义的


    文件定位函数(File Positioning Functions)

    fgetpos / fsetpos

    • 两者均由 C89 引入。

    • 失败时返回非零,并设置 errno 为实现定义的正值。


    错误处理函数(Error-Handling Functions)

    perror

    • 错误信息的内容和格式均为实现定义