文章目录

  • 1. 前言
  • 2. 输入/输出流
    • 2.1. 流
    • 2.2. 标准流
    • 2.3. 打开流
    • 2.4. 关闭流
    • 2.5. 流和线程
    • 2.6. 国际化应用程序中的流
    • 2.7. 按字符或行的简单输出
    • 2.8. 字符输入
    • 2.9. 面向行的输入
    • 2.10. 未读
      • 2.10.1. 未读意味着什么
      • 2.10.2. 使用ungetc做不读
    • 2.11. 块输入/输出
    • 2.12. 格式化输出
      • 2.12.1. 格式化输出基础
      • 2.12.2. 输出转换语法
      • 2.12.3. 输出转换表
      • 2.12.4. 整数转换
      • 2.12.5. 浮点转换
      • 2.12.6. 其他输出转换
      • 2.12.7. 格式化输出函数
      • 2.12.8. 动态分配格式化输出
      • 2.12.9. 变量参数输出函数
      • 2.12.10. 解析模板字符串
      • 2.12.11. 解析模板字符串的例子
    • 2.13. 自定义 printf
      • 2.13.1. 注册新转换
      • 2.13.2. 转换说明符选项
      • 2.13.3. 定义输出处理程序
      • 2.13.4. printf 扩展示例
      • 2.13.5. 预定义的 printf 处理程序
    • 2.14. 格式化输入
      • 2.14.1. 格式化输入基础
      • 2.14.2. 输入转换语法
      • 2.14.3. 输入转换表
      • 2.14.4. 数字输入转换
      • 2.14.5. 字符串输入转换
      • 2.14.6. 动态分配字符串转换
      • 2.14.7. 其他输入转换
      • 2.14.8. 格式化输入函数
      • 2.14.9. 变量参数输入函数
    • 2.15. 文件结束和错误
    • 2.16. 从错误中恢复
    • 2.17. 文本和二进制流
    • 2.18. 文件定位
    • 2.19. 便携式文件定位函数
    • 2.20. 流缓冲
      • 2.20.1. 缓冲概念
      • 2.20.2. 冲洗缓冲液
      • 2.20.3. 控制哪种缓冲
    • 2.21. 其他类型的流
      • 2.21.1. 字符串流
      • 2.21.2. 编写自己的自定义流
        • 2.21.2.1. 自定义流和 Cookie
        • 2.21.2.2. 自定义流挂钩函数
    • 2.22. 格式化消息
      • 2.22.1. 打印格式化消息
      • 2.22.2. 添加严重等级
      • 2.22.3. 如何使用 fmtmsg 和 addeverity
  • 3. 参考

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 输入/输出流

Input/Output on Streams

本章描述了创建流和对其执行输入和输出操作的函数。正如输入/输出概述中所讨论的,流是一个相当抽象的高级概念,表示文件、设备或进程的通信通道。

2.1. 流

Streams

由于历史原因,表示流的 C 数据结构的类型称为 FILE 而不是“stream”。由于大多数库函数都处理 FILE * 类型的对象,因此有时术语文件指针也用于表示“stream”。这导致许多关于 C 的书籍中的术语令人遗憾地混淆。然而,本手册谨慎地仅在技术意义上使用术语“file”和“stream”。

FILE 类型在头文件 stdio.h 中声明。

数据类型:FILE

这是用于表示流对象的数据类型。FILE 对象包含有关与关联文件的连接的所有内部状态信息,包括文件位置指示符和缓冲信息等。每个流还具有错误和文件结束状态指示器,可以使用 ferror 和 feof 函数进行测试;请参阅文件结尾和错误。

FILE 对象由输入/输出库函数在内部分配和管理。不要尝试创建自己的 FILE 类型的对象;让库去做。您的程序应该只处理指向这些对象的指针(即 FILE * 值)而不是对象本身。

2.2. 标准流

Standard Streams

当你的程序的 main 函数被调用时,它已经打开了三个预定义的流并可供使用。这些代表已为流程建立“标准”输入和输出通道。

这些流在头文件 stdio.h 中声明。

变量:FILE * stdin

标准输入流,它是程序的正常输入源。

变量:FILE * stdout

标准输出流,用于程序的正常输出。

变量:FILE * stderr

标准错误流,用于程序发出的错误消息和诊断。

在 GNU 系统上,您可以使用 shell 提供的管道和重定向工具指定哪些文件或进程与这些流相对应。(文件系统接口中描述了 shell 用来实现这些功能的原语。)大多数其他操作系统都提供了类似的机制,但如何使用它们的细节可能会有所不同。

在 GNU C 库中,stdin、stdout 和 stderr 是普通变量,您可以像设置任何其他变量一样设置它们。例如,要将标准输出重定向到文件,您可以执行以下操作:

fclose (stdout);
stdout = fopen ("standard-output-file", "w");

但是请注意,在其他系统中,stdin、stdout 和 stderr 是您无法以正常方式分配的宏。但是您可以使用 freopen 来获得关闭并重新打开它的效果。请参阅打开流。

三个流 stdin、stdout 和 stderr 在程序启动时不是无方向的(请参阅国际化应用程序中的流)。

2.3. 打开流

Opening Streams

使用 fopen 函数打开文件会创建一个新流并在流和文件之间建立连接。这可能涉及创建一个新文件。

本节中描述的所有内容都在头文件 stdio.h 中声明。

函数:FILE * fopen (const char *filename, const char *opentype)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem fd lock | See POSIX Safety Concepts.

fopen 函数为文件 filename 的 I/O 打开一个流,并返回一个指向该流的指针。

opentype 参数是一个字符串,它控制文件的打开方式并指定结果流的属性。它必须以下列字符序列之一开始:

‘r’

以只读方式打开现有文件。

‘w’

打开文件仅用于写入。如果文件已存在,则将其截断为零长度。否则会创建一个新文件。

‘a’

打开一个文件进行追加访问;也就是说,仅在文件末尾写入。如果文件已经存在,则其初始内容不变,流的输出将附加到文件的末尾。否则,将创建一个新的空文件。

‘r+’

打开现有文件以进行读取和写入。文件的初始内容不变,初始文件位置在文件的开头。

‘w+’

打开一个文件进行读写。如果文件已存在,则将其截断为零长度。否则,将创建一个新文件。

‘a+’

打开或创建用于读取和附加的文件。如果文件存在,则其初始内容不变。否则,将创建一个新文件。读取的初始文件位置在文件的开头,但输出始终附加到文件的末尾。

如您所见,“+”请求可以进行输入和输出的流。使用这样的流时,在从读切换到写时,必须调用 fflush(请参阅流缓冲)或文件定位函数,例如 fseek(请参阅文件定位),反之亦然。否则,可能无法正确清空内部缓冲区。

在这些之后可能会出现其他字符以指定调用的标志。始终将模式(‘r’、'w+'等)放在首位;这是唯一保证您将被所有系统理解的部分。

GNU C 库定义了用于 opentype 的附加字符:

‘c’

在禁用 I/O 功能的情况下打开文件。

‘e’

如果您使用任何 exec… 函数,底层文件描述符将被关闭(请参阅执行文件)。(这相当于在该描述符上设置了 FD_CLOEXEC。请参阅文件描述符标志。)

‘m’

使用 mmap 打开和访问该文件。这仅支持打开读取的文件。

‘x’

坚持创建一个新文件——如果一个文件文件名已经存在,fopen 会失败而不是打开它。如果您使用“x”,则可以保证您不会破坏现有文件。这等效于 open 函数的 O_EXCL 选项(请参阅打开和关闭文件)。

“x”修饰符是 ISO C11 的一部分,它表示文件是以独占访问权限创建的;在 GNU C 库中,这意味着等同于 O_EXCL。

opentype 中的字符’b’具有标准含义;它请求二进制流而不是文本流。但这对 POSIX 系统(包括 GNU 系统)没有影响。如果同时指定了“+”和“b”,它们可以按任意顺序出现。请参阅文本和二进制流。

如果 opentype 字符串包含序列 ,ccs=STRING 则 STRING 被视为编码字符集的名称,并且 fopen 将使用适当的转换函数将流标记为面向宽的流,以便在字符集 STRING 之间进行转换。任何其他流最初都是无方向打开的,方向由第一个文件操作决定。如果第一个操作是宽字符操作,则不仅将流标记为面向宽的,还加载转换为用于当前语言环境的编码字符集的转换函数。即使更改了为 LC_CTYPE 类别选择的语言环境,这也不会再更改。

opentype 中的任何其他字符都将被忽略。它们在其他系统中可能是有意义的。

如果打开失败,fopen 返回一个空指针。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 fopen64,因为 LFS 接口透明地替换了旧接口。

您可以同时打开多个指向同一个文件的流(或文件描述符)。如果你只做输入,这很简单,但如果包含任何输出流,你必须小心。请参阅混合流和描述符的危险。无论流是在一个程序中(不常见)还是在多个程序中(很容易发生),这同样适用。使用文件锁定设施来避免同时访问可能是有利的。请参阅文件锁。

函数:FILE * fopen64 (const char *filename, const char *opentype)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem fd lock | See POSIX Safety Concepts.

此函数类似于 fopen,但它返回指针的流是使用 open64 打开的。因此,这个流甚至可以用于 32 位机器上大于 2^31 字节的文件。

请注意,返回类型仍然是 FILE *。LFS 接口没有特殊的 FILE 类型。

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码,则此函数在名称 fopen 下可用,因此可以透明地替换旧接口。

宏:int FOPEN_MAX

该宏的值是一个整数常量表达式,表示实现保证可以同时打开的最小流数。您可能可以打开超过这么多的流,但这不能保证。该常量的值至少为 8,包括三个标准流 stdin、stdout 和 stderr。在 POSIX.1 系统中,该值由 OPEN_MAX 参数确定;请参阅一般容量限制。在 BSD 和 GNU 中,它由 RLIMIT_NOFILE 资源限制控制;请参阅限制资源使用。

函数:FILE * freopen (const char *filename, const char *opentype, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt fd | See POSIX Safety Concepts.

这个函数就像 fclose 和 fopen 的组合。它首先关闭 stream 引用的流,忽略在该过程中检测到的任何错误。(因为错误会被忽略,所以如果您实际上使用流完成了任何输出,则不应在输出流上使用 freopen。)然后以 filename 命名的文件以 fopen 的 opentype 模式打开,并与相同的流对象流相关联.

如果操作失败,则返回空指针;否则,freopen 返回流。在 Linux 上,当旧文件描述符的内核结构在调用 freopen 之前未完全初始化时,freopen 也可能会失败并将 errno 设置为 EBUSY。这只会发生在多线程程序中,当两个线程竞相分配相同的文件描述符编号时。为了避免这种竞争的可能性,不要使用 close 来关闭 FILE 的底层文件描述符;在文件仍然打开时使用 freopen,或者使用 open 然后 dup2 来安装新的文件描述符。

freopen 传统上用于将标准流(例如 stdin)与您自己选择的文件连接起来。这在为某些目的使用标准流是硬编码的程序中很有用。在 GNU C 库中,您可以简单地关闭标准流并使用 fopen 打开新流。但是其他系统缺乏这种能力,所以使用 freopen 更便携。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 freopen64,因为 LFS 接口透明地替换了旧接口。

函数:FILE * freopen64 (const char *filename, const char *opentype, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt fd | See POSIX Safety Concepts.

此功能类似于 freopen。唯一的区别是在 32 位机器上,返回的流能够读取超出正常接口强加的 2^31 字节限制。需要注意的是,stream 指向的流不需要使用 fopen64 或 freopen64 打开,因为它的模式对于这个函数并不重要。

如果源代码是在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译的,则此函数在名称 freopen 下可用,因此可以透明地替换旧接口。

在某些情况下,了解给定流是否可用于读取或写入很有用。此信息通常不可用,必须单独记住。Solaris 引入了一些函数来从流描述符中获取这些信息,这些函数在 GNU C 库中也可用。

函数:int __freadable (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

__freadable 函数确定流 stream 是否被打开以允许读取。在这种情况下,返回值是非零的。对于只写流,该函数返回零。

此函数在 stdio_ext.h 中声明。

函数:int __fwritable (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

__fwritable 函数确定流 stream 是否打开以允许写入。在这种情况下,返回值是非零的。对于只读流,该函数返回零。

此函数在 stdio_ext.h 中声明。

对于稍微不同类型的问题,还有另外两个功能。它们提供更细粒度的信息。

函数:int __freading (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

__freading 函数确定流 stream 是最后一次读取还是以只读方式打开。在这种情况下,返回值非零,否则为零。确定为读取和写入打开的流是否最后用于写入允许得出关于缓冲区内容的结论,等等。

此函数在 stdio_ext.h 中声明。

函数:int __fwriting (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

__fwriting 函数确定流 stream 是最后一次写入还是以只写方式打开。在这种情况下,返回值非零,否则为零。

此函数在 stdio_ext.h 中声明。

2.4. 关闭流

Closing Streams

当使用 fclose 关闭流时,会取消流与文件之间的连接。关闭流后,您无法对其执行任何其他操作。

函数:int fclose (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe lock mem fd | See POSIX Safety Concepts.

这个函数导致 stream 被关闭并且到相应文件的连接被中断。任何缓冲的输出都会被写入,任何缓冲的输入都会被丢弃。如果文件成功关闭,fclose 函数返回值 0,如果检测到错误,则返回 EOF。

在调用 fclose 关闭输出流时检查错误很重要,因为此时可以检测到真实的日常错误。例如,当 fclose 写入剩余的缓冲输出时,它可能会因为磁盘已满而出错。即使您知道缓冲区是空的,如果您使用 NFS,在关闭文件时仍然会发生错误。

函数 fclose 在 stdio.h 中声明。

为了关闭当前可用的所有流,GNU C 库提供了另一个功能。

函数:int fcloseall (void)

Preliminary: | MT-Unsafe race:streams | AS-Unsafe | AC-Safe | See POSIX Safety Concepts.

此函数会导致进程的所有打开流关闭,并断开与相应文件的连接。所有缓冲的数据都被写入,任何缓冲的输入都被丢弃。如果所有文件都成功关闭,则 fcloseall 函数返回值 0,如果检测到错误,则返回 EOF。

此功能应仅在特殊情况下使用,例如,当发生错误并且必须中止程序时。通常每个单独的流应该单独关闭,以便可以识别各个流的问题。这也是有问题的,因为标准流(参见标准流)也将被关闭。

函数 fcloseall 在 stdio.h 中声明。

如果您的程序的 main 函数返回,或者如果您调用 exit 函数(请参阅正常终止),所有打开的流都会自动正确关闭。如果您的程序以任何其他方式终止,例如通过调用 abort 函数(请参阅中止程序)或从致命信号(请参阅信号处理)中终止,则打开的流可能无法正确关闭。缓冲的输出可能不会被刷新并且文件可能不完整。有关流缓冲的更多信息,请参阅流缓冲。

2.5. 流和线程

Streams and Threads

流可以在多线程应用程序中使用,就像在单线程应用程序中使用一样。但是程序员必须意识到可能的复杂性。如果编写的程序从不使用线程,那么了解这些也很重要,因为许多流函数的设计和实现受到多线程编程所增加的要求的严重影响。

POSIX 标准要求默认情况下流操作是原子的。即,同时在两个线程中为同一流发出两个流操作将导致操作被执行,就好像它们是按顺序发出的一样。读取或写入时执行的缓冲区操作受到保护,不会被同一流用于其他用途。为此,每个流都有一个内部锁对象,在完成任何工作之前必须(隐式)获取该对象。

但在某些情况下这还不够,也有一些情况是不需要的。如果程序需要多个流函数调用以原子方式发生,则隐式锁定是不够的。一个例子是,如果程序想要生成的输出行是由多个函数调用创建的。函数本身只能确保它们自己操作的原子性,而不是所有函数调用的原子性。为此,有必要在应用程序代码中执行流锁定。

函数:void flockfile (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

flockfile 函数获取与流相关联的内部锁定对象。这确保没有其他线程可以通过flockfile/ftrylockfile 显式或通过调用流函数隐式锁定流。线程将阻塞,直到获得锁。必须使用对 funlockfile 的显式调用来释放锁。

函数:int ftrylockfile (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

ftrylockfile 函数尝试获取与流 stream 相关的内部锁定对象,就像flockfile 一样。但与flockfile 不同,如果锁不可用,此函数不会阻塞。如果锁被成功获取,ftrylockfile 返回零。否则流被另一个线程锁定。

函数:void funlockfile (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

funlockfile 函数释放流 stream 的内部锁定对象。该流必须在之前通过调用flockfile 或成功调用ftrylockfile 被锁定。流操作执行的隐式锁定不计算在内。funlockfile 函数不返回错误状态,并且未定义当前线程未锁定的流的调用行为。

以下示例显示了如何使用上述函数自动生成输出行,即使在多线程应用程序中也是如此(是的,可以通过一次 fprintf 调用完成相同的工作,但有时不可能):

FILE *fp;
{…flockfile (fp);fputs ("This is test number ", fp);fprintf (fp, "%d\n", test);funlockfile (fp)
}

如果没有显式锁定,另一个线程可能会在 fputs 调用返回之后和调用 fprintf 之前使用流 fp,结果数字不跟在单词“number”之后。

从这个描述中可能已经很清楚,流中的锁定对象不是简单的互斥体。由于允许在同一线程中两次锁定同一流,因此锁定对象必须等效于递归互斥锁。这些互斥锁跟踪所有者和获取锁的次数。相同线程的相同数量的 funlockfile 调用对于完全解锁流是必要的。例如:

void
foo (FILE *fp)
{ftrylockfile (fp);fputs ("in foo\n", fp);/* This is very wrong!!!  */funlockfile (fp);
}

重要的是,只有在 ftrylockfile 函数成功锁定流时才调用 funlockfile 函数。因此,忽略 ftrylockfile 的结果总是错误的。这是没有意义的,否则会使用flockfile。上述代码的结果是 funlockfile 尝试释放尚未被当前线程锁定的流或过早释放流。代码应如下所示:

void
foo (FILE *fp)
{if (ftrylockfile (fp) == 0){fputs ("in foo\n", fp);funlockfile (fp);}
}

既然我们已经介绍了为什么需要锁定,那么有必要讨论不需要锁定的情况以及可以做什么。锁定操作(显式或隐式)不是免费的。即使不使用锁,成本也不是零。必须执行的操作需要在多处理器环境中安全的内存操作。由于此类系统中涉及许多本地缓存,因此成本非常高。因此,如果不需要,最好完全避免锁定——因为有问题的代码永远不会在两个或多个线程可能同时使用一个流的上下文中使用。这可以在大多数情况下为应用程序代码确定;对于可以在许多情况下使用的库代码,应该默认是保守的并使用锁定。

有两种基本机制可以避免锁定。第一种是使用流操作的 _unlocked 变体。POSIX 标准定义了其中的一些,而 GNU C 库添加了更多。这些函数变体的行为与名称不带后缀的函数一样,只是它们不锁定流。使用这些函数是非常可取的,因为它们可能更快。这不仅是因为避免了锁定操作本身。更重要的是,像 putc 和 getc 这样的函数非常简单,并且传统上(在引入线程之前)是作为宏实现的,如果缓冲区不为空,这些宏会非常快。随着锁定要求的增加,这些函数不再以宏的形式实现,因为它们会扩展为过多的代码。但是这些宏在新名称 putc_unlocked 和 getc_unlocked 下仍然具有相同的功能。这种可能巨大的速度差异也表明即使需要锁定也可以使用 _unlocked 函数。不同之处在于必须在程序中执行锁定:

void
foo (FILE *fp, char *buf)
{flockfile (fp);while (*buf != '/')putc_unlocked (*buf++, fp);funlockfile (fp);
}

如果在此示例中使用 putc 函数并且缺少显式锁定,则 putc 函数将必须在每次调用中获取锁定,可能多次,具体取决于循环终止的时间。以上面说明的方式编写它允许使用 putc_unlocked 宏,这意味着没有锁定和直接操作流的缓冲区。

避免锁定的第二种方法是使用在 Solaris 中引入并且在 GNU C 库中也可用的非标准函数。

函数:int __fsetlocking (FILE *stream, int type)

Preliminary: | MT-Safe race:stream | AS-Unsafe lock | AC-Safe | See POSIX Safety Concepts.

__fsetlocking 函数可用于选择流操作是否会隐式获取流 stream 的锁定对象。默认情况下已完成,但可以使用此功能禁用和恢复。为 type 参数定义了三个值。

FSETLOCKING_INTERNAL

从现在开始,流 stream 将使用默认的内部锁定。除了 _unlocked 变体之外的每个流操作都将隐式锁定流。

FSETLOCKING_BYCALLER

__fsetlocking 函数返回后,用户负责锁定流。在状态设置回 FSETLOCKING_INTERNAL 之前,任何流操作都不会隐式执行此操作。

FSETLOCKING_QUERY

__fsetlocking 只查询流的当前锁定状态。根据状态,返回值将是 FSETLOCKING_INTERNAL 或 FSETLOCKING_BYCALLER。

__fsetlocking 的返回值是 FSETLOCKING_INTERNAL 或 FSETLOCKING_BYCALLER,具体取决于调用前流的状态。

此函数和类型参数的值在 stdio_ext.h 中声明。

当必须使用在不了解 _unlocked 函数的情况下编写的程序代码时(或者如果程序员懒得使用它们),此函数特别有用。

2.6. 国际化应用程序中的流

Streams in Internationalized Applications

ISO C90 引入了新类型 wchar_t 以允许处理更大的字符集。缺少的是直接输出 wchar_t 字符串的可能性。必须使用 mbstowcs(还没有 mbsrtowcs)将它们转换为多字节字符串,然后使用普通的流函数。虽然这是可行的,但非常麻烦,因为执行转换并非易事,并且大大增加了程序的复杂性和大小。

早期的 Unix 标准(我认为是 XPG4.2)为 printf 和 scanf 系列函数引入了两个额外的格式说明符。使用 %C 说明符可以打印和读取单个宽字符,并且可以使用 %S 处理宽字符串。这些修饰符的行为与 %c 和 %s 一样,只是它们希望相应的参数具有宽字符类型,并且在使用宽字符和字符串之前将其转换为/来自多字节字符串。

这是一个开始,但还不够好。并不总是需要使用 printf 和 scanf。其他更小更快的函数无法处理宽字符。其次,printf 和 scanf 的格式字符串不可能由宽字符组成。结果是,如果格式字符串必须包含非基本字符,则必须生成格式字符串。

在 ISO C90 的修正案 1 中添加了一套全新的功能来解决这个问题。大多数流函数都有对应的对象,它们分别采用宽字符或宽字符串而不是字符或字符串。新函数在相同的流上运行(如 stdout)。这与 C++ 运行时库的模型不同,后者为宽 I/O 和普通 I/O 使用单独的流。

能够将相同的流用于宽操作和普通操作有一个限制:一个流可以用于宽操作或普通操作。一旦决定了,就没有回头路了。只有调用 freopen 或 freopen64 才能重置方向。方向可以通过三种方式确定:

  • 如果使用了任何普通字符函数(包括 fread 和 fwrite 函数),则流被标记为不是面向宽的。
  • 如果使用任何宽字符函数,则流被标记为面向宽的。
  • fwide 函数可用于以任一方式设置方向。

重要的是永远不要在流上混合使用宽和非宽操作。没有发出诊断信息。应用程序的行为会很奇怪,或者应用程序会崩溃。fwide 函数可以帮助避免这种情况。

函数:int fwide (FILE *stream, int mode)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock | See POSIX Safety Concepts.

fwide 函数可用于设置和查询流 stream 方向的状态。如果 mode 参数具有正值,则流将面向宽,对于负值,则面向窄。无法用 fwide 覆盖以前的方向。即,如果流在调用之前已经定向,则什么也不做。

如果 mode 为零,则查询当前方向状态并且没有任何更改。

fwide 函数分别返回负值、零或正值,如果流是窄的、根本不是流向的,或者是宽向的。

该函数在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。

尽早确定流的方向通常是个好主意。这可以防止意外,尤其是对于标准流 stdin、stdout 和 stderr。如果某些库函数在某些情况下使用这些流之一,并且这种使用以不同的方式定向流,则应用程序的其余部分期望它最终可能会出现难以重现的错误。请记住,如果流使用不正确,则不会发出错误信号。在创建后保持流无方向通常仅对于创建可在不同上下文中使用的流的库函数是必需的。

在编写使用流并且可以在不同上下文中使用的代码时,在使用流之前查询流的方向很重要(除非库接口的规则要求特定的方向)。以下小而愚蠢的功能说明了这一点。

void
print_f (FILE *fp)
{if (fwide (fp, 0) > 0)/* Positive return value means wide orientation.  */fputwc (L'f', fp);elsefputc ('f', fp);
}

请注意,在这种情况下,函数 print_f 决定流的方向,如果它之前是无方向的(如果遵循上述建议,则不会发生)。

用于 wchar_t 值的编码未指定,用户不得对其进行任何假设。对于 wchar_t 值的 I/O,这意味着无法将这些值直接写入流。这也不是 ISO C 语言环境模型所遵循的。相反,从底层媒体读取或写入的字节首先转换为 wchar_t 实现选择的内部编码。外部编码由当前语言环境的 LC_CTYPE 类别或由 fopen、fopen64、freopen 或 freopen64 的模式规范的“ccs”部分确定。转换发生的方式和时间是未指定的,并且它对用户是不可见的。

由于流是在无方向状态下创建的,因此此时没有与之关联的转换。将使用的转换由流定向时选择的 LC_CTYPE 类别决定。如果在运行时更改语言环境,除非注意,否则可能会产生令人惊讶的结果。这只是尽快显式定向流的另一个很好的理由,可能需要调用 fwide。

2.7. 按字符或行的简单输出

Simple Output by Characters or Lines

本节介绍用于执行面向字符和面向行的输出的函数。

这些窄流函数在头文件 stdio.h 中声明,宽流函数在 wchar.h 中声明。

函数:int fputc (int c, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

fputc 函数将字符 c 转换为 unsigned char 类型,并将其写入流流。如果发生写入错误,则返回 EOF;否则返回字符 c。

函数:wint_t fputwc (wchar_t wc, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

fputwc 函数将宽字符 wc 写入流流。如果发生写入错误,则返回 WEOF;否则返回字符 wc。

函数:int fputc_unlocked (int c, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fputc_unlocked 函数等效于 fputc 函数,只是它不隐式锁定流。

函数:wint_t fputwc_unlocked (wchar_t wc, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fputwc_unlocked 函数与 fputwc 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int putc (int c, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

这就像 fputc,除了大多数系统将它实现为宏,使其更快。一个后果是它可能不止一次地评估流参数,这是宏的一般规则的一个例外。putc 通常是用于编写单个字符的最佳函数。

函数:wint_t putwc (wchar_t wc, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

这就像 fputwc 一样,只是它可以实现为宏,使其更快。一个后果是它可能不止一次地评估流参数,这是宏的一般规则的一个例外。putwc 通常是用于编写单个宽字符的最佳函数。

函数:int putc_unlocked (int c, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

putc_unlocked 函数与 putc 函数等效,只是它不隐式锁定流。

函数:wint_t putwc_unlocked (wchar_t wc, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

putwc_unlocked 函数与 putwc 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int putchar (int c)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

putchar 函数等效于 putc ,其中 stdout 作为流参数的值。

函数:wint_t putwchar (wchar_t wc)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

putwchar 函数等效于 putwc,其中 stdout 作为流参数的值。

函数:int putchar_unlocked (int c)

Preliminary: | MT-Unsafe race:stdout | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

putchar_unlocked 函数与 putchar 函数等效,只是它不隐式锁定流。

函数:wint_t putwchar_unlocked (wchar_t wc)

Preliminary: | MT-Unsafe race:stdout | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

putwchar_unlocked 函数与 putwchar 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int fputs (const char *s, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

函数 fputs 将字符串 s 写入流流。不写入终止空字符。此函数也不添加换行符。它只输出字符串中的字符。

如果发生写入错误,此函数返回 EOF,否则返回非负值。

例如:

fputs ("Are ", stdout);
fputs ("you ", stdout);
fputs ("hungry?\n", stdout);

输出文本“你饿了吗?”后跟换行符。

函数:int fputws (const wchar_t *ws, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe corrupt lock | See POSIX Safety Concepts.

函数 fputws 将宽字符串 ws 写入流流。不写入终止空字符。此函数也不添加换行符。它只输出字符串中的字符。

如果发生写入错误,此函数返回 WEOF,否则返回非负值。

函数:int fputs_unlocked (const char *s, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fputs_unlocked 函数与 fputs 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int fputws_unlocked (const wchar_t *ws, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fputws_unlocked 函数与 fputws 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int puts (const char *s)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

puts 函数将字符串 s 写入流 stdout,后跟换行符。不写入字符串的终止空字符。(注意 fputs 不像这个函数那样写换行符。)

puts 是打印简单消息最方便的函数。例如:

puts ("This is a message.");

输出文本“这是一条消息。”后跟换行符。

函数:int putw (int w, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数将单词 w(即一个 int)写入流。提供它是为了与 SVID 兼容,但我们建议您改用 fwrite(请参阅块输入/输出)。

2.8. 字符输入

Character Input

本节介绍执行面向字符输入的函数。这些窄流函数在头文件 stdio.h 中声明,宽字符函数在 wchar.h 中声明。

这些函数返回一个 int 或 wint_t 值(分别用于窄流和宽流函数),它可以是输入字符,也可以是特殊值 EOF/WEOF(通常为 -1)。对于窄流函数,将这些函数的结果存储在 int 类型的变量中而不是 char 中很重要,即使您打算仅将其用作字符。将 EOF 存储在 char 变量中会将其值截断为字符的大小,因此它不再与有效字符“(char) -1”区分开来。所以getc和friends的结果总是用int,调用后检查EOF;一旦你确认结果不是 EOF,你就可以确定它会适合一个“char”变量而不会丢失信息。

函数:int fgetc (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数从流中读取下一个字符作为 unsigned char 并返回其值,转换为 int。如果发生文件结束条件或读取错误,则返回 EOF。

函数:wint_t fgetwc (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数从流流中读取下一个宽字符并返回其值。如果发生文件结束条件或读取错误,则返回 WEOF。

函数:int fgetc_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fgetc_unlocked 函数与 fgetc 函数等效,只是它不隐式锁定流。

函数:wint_t fgetwc_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fgetwc_unlocked 函数与 fgetwc 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int getc (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

这就像 fgetc 一样,除了它是允许的(并且是典型的)它被实现为不止一次评估流参数的宏。getc 通常是高度优化的,因此它通常是用于读取单个字符的最佳函数。

函数:wint_t getwc (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

这就像 fgetwc 一样,不同之处在于它可以实现为不止一次计算流参数的宏。getwc 可以进行高度优化,因此它通常是用于读取单个宽字符的最佳函数。

函数:int getc_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

getc_unlocked 函数与 getc 函数等效,只是它不隐式锁定流。

函数:wint_t getwc_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

getwc_unlocked 函数与 getwc 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:int getchar (void)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

getchar 函数等效于 getc,其中 stdin 作为流参数的值。

函数:wint_t getwchar (void)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

getwchar 函数等效于 getwc,其中 stdin 作为流参数的值。

函数:int getchar_unlocked (void)

Preliminary: | MT-Unsafe race:stdin | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

getchar_unlocked 函数与 getchar 函数等效,只是它不隐式锁定流。

函数:wint_t getwchar_unlocked (void)

Preliminary: | MT-Unsafe race:stdin | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

getwchar_unlocked 函数与 getwchar 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

这是一个使用 fgetc 进行输入的函数的示例。使用 getc 或使用 getchar () 代替 fgetc (stdin) 也可以正常工作。该代码也适用于宽字符流函数。

int
y_or_n_p (const char *question)
{fputs (question, stdout);while (1){int c, answer;/* Write a space to separate answer from question. */fputc (' ', stdout);/* Read the first character of the line.This should be the answer character, but might not be. */c = tolower (fgetc (stdin));answer = c;/* Discard rest of input line. */while (c != '\n' && c != EOF)c = fgetc (stdin);/* Obey the answer if it was valid. */if (answer == 'y')return 1;if (answer == 'n')return 0;/* Answer was invalid: ask for valid answer. */fputs ("Please answer y or n:", stdout);}
}

函数:int getw (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数从流中读取一个单词(即一个 int)。提供它是为了与 SVID 兼容。我们建议您改用 fread(请参阅块输入/输出)。与 getc 不同,任何 int 值都可以是有效结果。getw 在遇到文件结尾或错误时返回 EOF,但无法将其与值为 -1 的输入字区分开来。

2.9. 面向行的输入

Line-Oriented Input

由于许多程序基于行解释输入,因此具有从流中读取一行文本的函数很方便。

标准 C 具有执行此操作的函数,但它们不是很安全:空字符甚至(用于获取)长行都会使它们感到困惑。因此 GNU C 库提供了非标准的 getline 函数,可以轻松可靠地读取行。

另一个 GNU 扩展 getdelim 概括了 getline。它读取一个分隔记录,定义为通过下一次出现指定分隔符字符的所有内容。

所有这些函数都在 stdio.h 中声明。

函数:ssize_t getline (char **lineptr, size_t *n, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt heap | AC-Unsafe lock corrupt mem | See POSIX Safety Concepts.

该函数从流中读取整行,将文本(包括换行符和终止空字符)存储在缓冲区中,并将缓冲区地址存储在 *lineptr 中。

在调用 getline 之前,您应该在 *lineptr 中放置一个长度为 *n 字节的缓冲区的地址,该地址由 malloc 分配。如果此缓冲区足够长以容纳该行,则 getline 将该行存储在此缓冲区中。否则,getline 使用 realloc 使缓冲区更大,将新的缓冲区地址存储回 *lineptr 并将增加的大小存储回 *n。请参阅无约束分配。

如果在调用之前将 *lineptr 设置为空指针,并将 *n 设置为零,则 getline 通过调用 malloc 为您分配初始缓冲区。即使 getline 遇到错误并且无法读取任何字节,此缓冲区仍保持分配状态。

无论哪种情况,当 getline 返回时,*lineptr 是一个 char *,它指向该行的文本。

当 getline 成功时,它返回读取的字符数(包括换行符,但不包括终止的 null)。此值使您能够将作为行的一部分的空字符与作为终止符插入的空字符区分开来。

此函数是 GNU 扩展,但它是从流中读取行的推荐方法。替代标准功能不可靠。

如果发生错误或在没有读取任何字节的情况下到达文件末尾,getline 将返回 -1。

函数:ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt heap | AC-Unsafe lock corrupt mem | See POSIX Safety Concepts.

这个函数类似于 getline,只是告诉它停止阅读的字符不一定是换行符。参数 delimiter 指定分隔符; getdelim 一直读取,直到它看到该字符(或文件结尾)。

文本存储在 lineptr 中,包括分隔符和终止空值。与 getline 一样,getdelim 会使 lineptr 不够大。

getline 实际上是用 getdelim 来实现的,就像这样:

ssize_t
getline (char **lineptr, size_t *n, FILE *stream)
{return getdelim (lineptr, n, '\n', stream);
}

函数:char * fgets (char *s, int count, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

fgets 函数从流中读取字符直到换行符(包括换行符),并将它们存储在字符串 s 中,添加一个空字符来标记字符串的结尾。您必须在 s 中提供 count 个字符的空格,但读取的字符数最多为 count - 1。额外的字符空间用于在字符串末尾保存空字符。

如果调用 fgets 时系统已经在文件末尾,则数组 s 的内容不变,并返回一个空指针。如果发生读取错误,也会返回一个空指针。否则,返回值为指针 s。

警告:如果输入数据有一个空字符,你无法分辨。因此,除非您知道数据不能包含空值,否则不要使用 fgets。不要用它来读取用户编辑的文件,因为如果用户插入一个空字符,你应该正确处理它或者打印一个明确的错误信息。我们建议使用 getline 而不是 fgets。

函数:wchar_t * fgetws (wchar_t *ws, int count, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

fgetws 函数从流中读取宽字符直到换行符(包括换行符),并将它们存储在字符串 ws 中,添加一个空宽字符来标记字符串的结尾。您必须在 ws 中提供 count 个宽字符的空间,但读取的字符数最多为 count - 1。额外的字符空间用于在字符串末尾保存空宽字符。

如果调用 fgetws 时系统已经在文件末尾,则数组 ws 的内容保持不变并返回一个空指针。如果发生读取错误,也会返回一个空指针。否则,返回值为指针ws。

警告:如果输入数据有一个空宽字符(输入流中的空字节),你无法分辨。因此,除非您知道数据不能包含空值,否则不要使用 fgetws。不要用它来读取用户编辑的文件,因为如果用户插入一个空字符,你应该正确处理它或者打印一个明确的错误信息。

函数:char * fgets_unlocked (char *s, int count, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fgets_unlocked 函数与 fgets 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:wchar_t * fgetws_unlocked (wchar_t *ws, int count, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fgetws_unlocked 函数与 fgetws 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

不推荐使用的函数:char * gets (char *s)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

该函数从流 stdin 中读取字符直到下一个换行符,并将它们存储在字符串 s 中。换行符被丢弃(请注意,这与 fgets 的行为不同,后者将换行符复制到字符串中)。如果gets遇到读取错误或文件结束,则返回一个空指针;否则返回 s。

警告:gets 函数非常危险,因为它不提供防止字符串 s 溢出的保护。GNU C 库包含它只是为了兼容。您应该始终使用 fgets 或 getline 。为了提醒您这一点,链接器(如果使用 GNU ld)将在您使用 get 时发出警告。

2.10. 未读

Unreading

在解析器程序中,检查输入流中的下一个字符而不将其从流中删除通常很有用。这被称为输入的“窥视”,因为您的程序可以瞥见接下来将读取的输入。

使用流 I/O,您可以先读取输入,然后再取消读取(也称为将其推回流上),从而提前查看输入。通过下一次调用 fgetc 或该流上的其他输入函数,未读取字符使其可再次从流中输入。

2.10.1. 未读意味着什么

这是未读的图示说明。假设您有一个流正在读取一个只包含六个字符的文件,即字母“foobar”。假设您到目前为止已经阅读了三个字符。情况如下所示:

f  o  o  b  a  r^

所以下一个输入字符将是’b’。

如果你没有读到“b”,而没有读到“o”,你会遇到这样的情况:

f  o  o  b  a  r|o--^

这样下一个输入字符将是“o”和“b”。

如果您未读“9”而不是“o”,则会出现以下情况:

f  o  o  b  a  r|9--^

这样下一个输入字符将是“9”和“b”。

2.10.2. 使用ungetc做不读

Using ungetc To Do Unreading

取消读取字符的函数称为 ungetc,因为它反转了 getc 的操作。

函数:int ungetc (int c, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

ungetc 函数将字符 c 推回输入流流中。因此,来自流的下一个输入将首先读取 c 。

如果 c 是 EOF,ungetc 什么也不做,只返回 EOF。这使您可以使用 getc 的返回值调用 ungetc,而无需检查来自 getc 的错误。

您推回的字符不必与实际从流中读取的最后一个字符相同。事实上,在使用 ungetc 取消读取之前,实际上没有必要从流中读取任何字符!但这是一种奇怪的编写程序的方式。通常 ungetc 仅用于未读取刚刚从同一流中读取的字符。GNU C 库甚至在以二进制模式打开的文件上也支持这一点,但其他系统可能不支持。

GNU C 库只支持一个字符的 pushback — 换句话说,调用 ungetc 两次而不在其间进行输入是行不通的。其他系统可能会让你推回多个字符;然后从流中读取以与推送字符相反的顺序检索字符。

推回字符不会改变文件;只有流的内部缓冲受到影响。如果调用了文件定位函数(例如 fseek、fseeko 或 rewind;请参阅文件定位),则任何未决的推回字符都将被丢弃。

取消读取位于文件末尾的流上的字符会清除流的文件结束指示符,因为它使输入的字符可用。读取该字符后,再次尝试读取将遇到文件结尾。

函数:wint_t ungewc (wint_t wc, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

ungewc 函数的行为就像 ungetc 一样,只是它推回一个宽字符。

这是一个使用 getc 和 ungetc 跳过空白字符的示例。当此函数到达非空白字符时,它会取消读取该字符,以便在流的下一次读取操作中再次看到该字符。

#include <stdio.h>
#include <ctype.h>void
skip_whitespace (FILE *stream)
{int c;do/* No need to check for EOF because it is notisspace, and ungetc ignores EOF.  */c = getc (stream);while (isspace (c));ungetc (c, stream);
}

2.11. 块输入/输出

Block Input/Output

本节介绍如何对数据块进行输入和输出操作。您可以使用这些函数来读取和写入二进制数据,以及以固定大小的块而不是字符或行来读取和写入文本。

二进制文件通常用于以与在运行程序中表示数据相同的格式读取和写入数据块。换句话说,可以将任意内存块(不仅仅是字符或字符串对象)写入二进制文件,并由同一个程序再次有意义地读入。

以二进制形式存储数据通常比使用格式化的 I/O 函数更有效。此外,对于浮点数,二进制形式避免了转换过程中可能的精度损失。另一方面,二进制文件不能使用许多标准文件实用程序(例如文本编辑器)轻松检查或修改,并且不能在语言的不同实现或不同类型的计算机之间移植。

这些函数在 stdio.h 中声明。

函数:size_t fread (void *data, size_t size, size_t count, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数从流流中读取最多 count 个大小的对象到数组数据中。它返回实际读取的对象数,如果发生读取错误或到达文件末尾,则可能小于 count。如果 size 或 count 为零,则此函数返回零值(并且不读取任何内容)。

如果 fread 在对象中间遇到文件结尾,则返回读取的完整对象的数量,并丢弃部分对象。因此,流保留在文件的实际末尾。

函数:size_t fread_unlocked (void *data, size_t size, size_t count, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fread_unlocked 函数与 fread 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

函数:size_t fwrite (const void *data, size_t size, size_t count, FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数将数组数据中大小为 size 的对象最多写入到流流中。如果调用成功,返回值通常是 count。任何其他值都表示某种错误,例如空间不足。

函数:size_t fwrite_unlocked (const void *data, size_t size, size_t count, FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fwrite_unlocked 函数与 fwrite 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

2.12. 格式化输出

Formatted Output

本节中描述的函数(printf 和相关函数)提供了一种方便的方式来执行格式化输出。您调用 printf 时使用格式字符串或模板字符串,指定如何格式化剩余参数的值。

除非您的程序是专门执行面向行或面向字符的处理的过滤器,否则使用 printf 或本节中描述的其他相关函数之一通常是执行输出的最简单和最简洁的方法。这些函数对于打印错误消息、数据表等特别有用。

2.12.1. 格式化输出基础

Formatted Output Basics

printf 函数可用于打印任意数量的参数。您在调用中提供的模板字符串参数不仅提供有关附加参数数量的信息,还提供有关它们的类型以及应该使用什么样式来打印它们的信息。

模板字符串中的普通字符简单地按原样写入输出流,而模板中由“%”字符引入的转换规范会导致后续参数被格式化并写入输出流。例如,

int pct = 37;
char filename[] = "foo.txt";
printf ("Processing of `%s' is %d%% finished.\nPlease be patient.\n",filename, pct);

产生像这样的输出

Processing of `foo.txt' is 37% finished.
Please be patient.

此示例显示使用 ‘%d’ 转换来指定应以十进制表示法打印 int 参数,使用 ‘%s’ 转换来指定打印字符串参数,以及使用 ‘%%’ 转换来打印文字’%’ 特点。

还有将整数参数打印为二进制、八进制、十进制或十六进制基数(分别为“%b”、“%o”、“%u”或“%x”)的无符号值的转换;或作为字符值 (‘%c’)。

浮点数可以使用“%f”转换以普通定点表示法打印,也可以使用“%e”转换以指数表示法打印。“%g”转换使用“%e”或“%f”格式,具体取决于更适合特定数字大小的格式。

您可以通过在“%”和指示要应用哪种转换的字符之间写入修饰符来更精确地控制格式。这些稍微改变了转换的普通行为。例如,大多数转换规范允许您指定最小字段宽度和一个标志,指示您希望结果在字段内左对齐还是右对齐。

允许的特定标志和修饰符及其解释因特定转换而异。它们都在以下部分中进行了更详细的描述。如果这一切一开始看起来过于复杂,请不要担心;您几乎总是可以在不使用任何修饰符的情况下获得合理的自由格式输出。修饰符主要用于使输出在表格中看起来“更漂亮”。

2.12.2. 输出转换语法

Output Conversion Syntax

本节提供有关可出现在 printf 模板字符串中的转换规范的精确语法的详细信息。

模板字符串中不属于转换规范的字符按原样打印到输出流。模板字符串中允许使用多字节字符序列(请参阅字符集处理)。

printf 模板字符串中的转换规范具有以下一般形式:

% [ param-no $] flags width [ . precision ] type conversion

或者

% [ param-no $] flags width . * [ param-no $] type conversion

例如,在转换说明符’%-10.8ld’中,'-'是一个标志,‘10’指定字段宽度,精度是’8’,字母’l’是类型修饰符,'d ’ 指定转换样式。(这个特定的类型说明符表示以十进制表示法打印一个 long int 参数,在一个至少 10 个字符宽的字段中至少有 8 位左对齐。)

更详细地说,输出转换规范由一个初始的“%”字符组成,后面依次是:

  • 用于此格式的参数的可选规范。通常,printf 函数的参数按照出现在格式字符串中的顺序分配给格式。但在某些情况下(例如消息翻译),这是不可取的,并且此扩展允许指定显式参数。

    格式的 param-no 部分必须是介于 1 到函数调用的最大参数数之间的整数。一些实现将此数字限制为某个上限。可以通过以下常量检索确切的限制。

    宏:NL_ARGMAX

    NL_ARGMAX 的值是 printf 调用中指定位置参数所允许的最大值。运行时有效的实际值可以通过使用 sysconf 使用 _SC_NL_ARGMAX 参数来检索,请参见 sysconf 的定义。

    某些系统的限制非常低,例如 System V 系统的限制为 9。GNU C 库没有真正的限制。

    如果任何格式都有参数位置的规范,则格式字符串中的所有格式都应有一个。否则行为未定义。

  • 零个或多个修改转换规范的正常行为的标志字符。

  • 一个可选的十进制整数,指定最小字段宽度。如果正常转换产生的字符数少于此值,则该字段将用空格填充到指定的宽度。这是一个最小值;如果正常转换产生比这更多的字符,则不会截断该字段。通常,输出在字段内是右对齐的。

    您还可以指定“*”的字段宽度。这意味着参数列表中的下一个参数(在要打印的实际值之前)用作字段宽度。该值必须是 int。如果该值为负数,这意味着设置“-”标志(见下文)并使用绝对值作为字段宽度。

  • 一个可选精度,用于指定要为数字转换写入的位数。如果指定了精度,则它由一个句点 (‘.’) 组成,后跟一个可选的十进制整数(如果省略,则默认为零)。

    您还可以指定精度为“*”。这意味着参数列表中的下一个参数(在要打印的实际值之前)用作精度。该值必须是一个 int,如果它是负数则被忽略。如果同时为字段宽度和精度指定“*”,则字段宽度参数在精度参数之前。其他 C 库版本可能无法识别此语法。

  • 可选类型修饰符,用于指定对应参数的数据类型(如果它不同于默认类型)。(例如,整数转换假定为 int 类型,但您可以为其他整数类型指定“h”、“l”或“L”。)

  • 指定要应用的转换的字符。

允许的确切选项以及它们的解释方式因不同的转换说明符而异。有关它们使用的特定选项的信息,请参阅各个转换的描述。

使用“-Wformat”选项,GNU C 编译器检查对 printf 和相关函数的调用。它检查格式字符串并验证是否提供了正确数量和类型的参数。还有一种 GNU C 语法告诉编译器您编写的函数使用 printf 样式的格式字符串。有关更多信息,请参阅使用 GNU CC 中声明函数的属性。

2.12.3. 输出转换表

下表总结了所有不同转换的作用:

‘%d’, ‘%i’

将整数打印为有符号十进制数。有关详细信息,请参阅整数转换。‘%d’ 和 ‘%i’ 是输出的同义词,但在与 scanf 一起用于输入时是不同的(参见输入转换表)。

‘%b’, ‘%B’

将整数打印为无符号二进制数。‘%b’ 使用带有 ‘#’ 标志的小写 ‘b’ 而 ‘%B’ 使用大写。‘%b’ 是 ISO C2X 功能; “%B”是 ISO C2X 推荐的扩展。有关详细信息,请参阅整数转换。

‘%o’

将整数打印为无符号八进制数。有关详细信息,请参阅整数转换。

‘%u’

将整数打印为无符号十进制数。有关详细信息,请参阅整数转换。

‘%x’, ‘%X’

将整数打印为无符号十六进制数。“%x”使用小写字母,“%X”使用大写字母。有关详细信息,请参阅整数转换。

‘%f’

以普通(定点)表示法打印浮点数。有关详细信息,请参阅浮点转换。

‘%e’, ‘%E’

以指数表示法打印浮点数。“%e”使用小写字母,“%E”使用大写字母。有关详细信息,请参阅浮点转换。

‘%g’, ‘%G’

以正态或指数表示法打印浮点数,以更适合其大小为准。“%g”使用小写字母,“%G”使用大写字母。有关详细信息,请参阅浮点转换。

‘%a’, ‘%A’

以十六进制小数表示法打印浮点数,以十进制数字表示以 2 为底的指数。“%a”使用小写字母,“%A”使用大写字母。有关详细信息,请参阅浮点转换。

‘%c’

打印单个字符。请参阅其他输出转换。

‘%C’

这是“%lc”的别名,支持与 Unix 标准兼容。

‘%s’

打印一个字符串。请参阅其他输出转换。

‘%S’

这是“%ls”的别名,支持与 Unix 标准兼容。

‘%p’

打印指针的值。请参阅其他输出转换。

‘%n’

获取到目前为止打印的字符数。请参阅其他输出转换。请注意,此转换规范从不产生任何输出。

‘%m’

打印与 errno 的值对应的字符串。(这是一个 GNU 扩展。)请参阅其他输出转换。

‘%%’

打印一个文字“%”字符。请参阅其他输出转换。

如果转换规范的语法无效,就会发生不可预知的事情,所以不要这样做。如果没有提供足够的函数参数来为模板字符串中的所有转换规范提供值,或者如果参数的类型不正确,则结果是不可预测的。如果您提供的参数多于转换规范,则简单地忽略额外的参数值;这有时很有用。

2.12.4. 整数转换

Integer Conversions

本节介绍“%d”、“%i”、“%b”、“%B”、“%o”、“%u”、“%x”和“%X”转换规范的选项。这些转换以各种格式打印整数。

‘%d’ 和 ‘%i’ 转换规范都将 int 参数打印为带符号的十进制数;而“b”、“%o”、“%u”和“%x”将参数打印为无符号二进制、八进制、十进制或十六进制数(分别)。“%X”转换规范与“%x”一样,只是它使用字符“ABCDEF”作为数字而不是“abcdef”。‘%B’ 转换规范与 ‘%b’ 类似,不同之处在于,使用 ‘#’ 标志,输出以 ‘0B’ 而不是 ‘0b’ 开头。

以下标志是有意义的:

‘-’

将字段中的结果左对齐(而不是正常的右对齐)。

‘+’

对于带符号的“%d”和“%i”转换,如果值为正,则打印一个加号。

’ ’

对于带符号的“%d”和“%i”转换,如果结果不是以加号或减号开头,请在其前面加上空格字符。由于“+”标志确保结果包含符号,因此如果您同时提供这两个标志,则忽略此标志。

‘#’

对于“%o”转换,这会强制前导数字为“0”,就好像通过提高精度一样。对于“%x”或“%X”,这会在结果前加前缀“0x”或“0X”(分别)。对于“%b”或“%B”,这会将前导“0b”或“0B”(分别)作为结果的前缀。这对“%d”、“%i”或“%u”转换没有任何用处。使用此标志会产生可由 strtoul 函数解析的输出(请参阅整数解析)和带有“%i”转换的 scanf(请参阅数字输入转换)。

对于“%m”转换,打印错误常量或十进制错误编号,而不是(可能已翻译的)错误消息。

‘’’

根据为 LC_NUMERIC 类别指定的区域设置将数字分成组;请参阅通用数字格式参数。这个标志是一个 GNU 扩展。

‘0’

用零而不是空格填充字段。零被放置在任何符号或基数的指示之后。如果还指定了“-”标志,或者指定了精度,则忽略此标志。

如果提供了精度,它指定出现的最小位数;必要时产生前导零。如果您未指定精度,则该数字将打印所需的位数。如果您使用显式精度为零转换零值,则根本不会产生任何字符。

如果没有类型修饰符,则相应的参数被视为 int(对于有符号转换 ‘%i’ 和 ‘%d’)或 unsigned int(对于无符号转换 ‘%b’、‘%B’、‘%o’ 、“%u”、“%x”和“%X”)。回想一下,由于 printf 和朋友都是可变参数,因此任何 char 和 short 参数都会通过默认参数提升自动转换为 int。对于其他整数类型的参数,您可以使用这些修饰符:

‘hh’

根据需要指定参数是有符号字符还是无符号字符。无论如何,默认参数提升会将 char 参数转换为 int 或 unsigned int,但“hh”修饰符表示再次将其转换回 char。

此修饰符是在 ISO C99 中引入的。

‘h’

指定参数是一个短整型或无符号短整型,视情况而定。无论如何,默认参数提升将 short 参数转换为 int 或 unsigned int,但“h”修饰符表示再次将其转换回 short。

‘j’

根据需要指定参数是 intmax_t 或 uintmax_t。

此修饰符是在 ISO C99 中引入的。

‘l’

指定参数是 long int 或 unsigned long int,视情况而定。两个“l”字符类似于下面的“L”修饰符。

如果与“%c”或“%s”一起使用,则相应的参数分别被视为宽字符或宽字符串。“l”的这种用法是在 ISO C90 的修正案 1 中引入的。

'L'
'll'
'q'

指定参数是 long long int。(此类型是 GNU C 编译器支持的扩展。在不支持超长整数的系统上,这与 long int 相同。)

‘q’ 修饰符是同一事物的另一个名称,它来自 4.4 BSD; long long int 有时称为“quad” int。

‘t’

指定参数是 ptrdiff_t。

此修饰符是在 ISO C99 中引入的。

'z'
'Z'

指定参数是 size_t。

“z”是在 ISO C99 中引入的。‘Z’ 是在此添加之前的 GNU 扩展,不应在新代码中使用。

这是一个例子。使用模板字符串:

"|%5d|%-5d|%+5d|%+-5d|% 5d|%05d|%5.0d|%5.2d|%d|\n"

使用“%d”转换的不同选项打印数字会得到如下结果:

|  0|0  | +0|+0 |  0|00000|   |  00|0|
|  1|1  | +1|+1 |  1|00001|  1|  01|1|
| -1|-1 | -1|-1 | -1|-0001| -1| -01|-1|
|100000|100000|+100000|+100000| 100000|100000|100000|100000|100000|

特别要注意在最后一种情况下发生的情况,即数字太大而无法容纳指定的最小字段宽度。

以下是更多示例,展示了如何使用模板字符串在各种格式选项下打印无符号整数:

"|%5u|%5o|%5x|%5X|%#5o|%#5x|%#5X|%#10.8x|\n"
| 0| 0| 0| 0|  0|   0|   0|  00000000|
| 1| 1| 1| 1| 01| 0x1| 0X1|0x00000001|
|100000|303240|186a0|186A0|0303240|0x186a0|0X186A0|0x000186a0|

2.12.5. 浮点转换

Floating-Point Conversions

本节讨论浮点数的转换规范:“%f”、“%e”、“%E”、“%g”和“%G”转换。

‘%f’ 转换以定点表示法打印其参数,生成 [-]ddd.ddd 形式的输出,其中小数点后的位数由您指定的精度控制。

‘%e’ 转换以指数表示法打印其参数,产生形式为 [-]d.ddde[+|-]dd 的输出。同样,小数点后的位数由精度控制。指数始终包含至少两位数。“%E”转换类似,但指数用字母“E”而不是“e”标记。

如果指数小于 -4 或大于或等于精度,则“%g”和“%G”转换以“%e”或“%E”的样式(分别)打印参数;否则他们使用“%f”样式。精度 0 被视为 1。从结果的小数部分中删除尾随零,并且仅当小数点字符后跟数字时才会出现小数点字符。

‘%a’ 和 ‘%A’ 转换用于以文本形式精确表示浮点数,以便它们可以在不同程序和/或机器之间作为文本交换。这些数字以 [-]0xh.hhhp[+|-]dd 的形式表示。在小数点字符的左边正好打印一位数字。如果数字非规范化,则此字符仅为 0。否则,该值未指定;它取决于使用了多少位的实现。小数点字符右侧的十六进制位数等于精度。如果精度为零,则确定它足够大以提供数字的精确表示(或者如果 FLT_RADIX 不是 2 的幂,则它足够大以区分两个相邻的值,请参阅浮点参数)。对于“%a”转换,小写字符用于表示十六进制数,前缀和指数符号分别打印为 0x 和 p。否则使用大写字符,0X 和 P 用于表示前缀和指数字符串。以 2 为底的指数打印为十进制数,至少使用一位数字,但最多可以使用尽可能多的数字来准确表示该值。

如果要打印的值表示无穷大或 NaN,则输出分别为 [-]inf 或 nan,如果转换说明符是 ‘%a’、‘%e’、‘%f’ 或 ‘%g’ 并且它是如果转换为“%A”、“%E”或“%G”,则分别为 [-]INF 或 NAN。在某些实现中,NaN 可能会导致更长的输出,其中包含有关 NaN 有效负载的信息; ISO C2X 定义了一个宏 _PRINTF_NAN_LEN_MAX,给出了这种输出的最大长度。

以下标志可用于修改行为:

‘-’

将字段中的结果左对齐。通常结果是右对齐的。

‘+’

始终在结果中包含加号或减号。

’ ’

如果结果不是以加号或减号开头,请在其前面加一个空格。由于“+”标志确保结果包含符号,因此如果您同时提供这两个标志,则忽略此标志。

‘#’

指定结果应始终包含小数点,即使它后面没有数字。对于“%g”和“%G”转换,这也会强制将小数点后的尾随零留在原处,否则它们会被删除。

‘’’

根据为 LC_NUMERIC 类别指定的语言环境将结果的整数部分的数字分成组;请参阅通用数字格式参数。这个标志是一个 GNU 扩展。

‘0’

用零而不是空格填充字段;零放在任何符号之后。如果还指定了“-”标志,则忽略此标志。

精度指定“%f”、“%e”和“%E”转换的小数点字符后面的位数。对于这些转换,默认精度为 6。如果精度显式为 0,这将完全抑制小数点字符。对于“%g”和“%G”转换,精度指定要打印的有效数字位数。有效数字是小数点之前的第一位数字,以及它之后的所有数字。如果 ‘%g’ 或 ‘%G’ 的精度为 0 或未指定,则将其视为值 1。如果打印的值不能以指定的位数准确表示,则将值四舍五入到最接近的数字。

如果没有类型修饰符,浮点转换将使用 double 类型的参数。(通过默认参数提升,任何浮点参数都会自动转换为双精度。)支持以下类型修饰符:

‘L’

大写的“L”指定参数是长双精度。

下面是一些示例,展示了如何使用各种浮点转换打印数字。所有数字都是使用此模板字符串打印的:

"|%13.4a|%13.4f|%13.4e|%13.4g|\n"

这是输出:

|  0x0.0000p+0|      0.0000|  0.0000e+00|         0|
|  0x1.0000p-1|      0.5000|  5.0000e-01|       0.5|
|  0x1.0000p+0|      1.0000|  1.0000e+00|         1|
| -0x1.0000p+0|     -1.0000| -1.0000e+00|        -1|
|  0x1.9000p+6|    100.0000|  1.0000e+02|       100|
|  0x1.f400p+9|   1000.0000|  1.0000e+03|      1000|
| 0x1.3880p+13|  10000.0000|  1.0000e+04|     1e+04|
| 0x1.81c8p+13|  12345.0000|  1.2345e+04| 1.234e+04|
| 0x1.86a0p+16| 100000.0000|  1.0000e+05|     1e+05|
| 0x1.e240p+16| 123456.0000|  1.2346e+05| 1.235e+05|

请注意“%g”转换如何丢弃尾随零。

2.12.6. 其他输出转换

本节介绍 printf 的各种转换。

‘%c’ 转换打印单个字符。如果没有“l”修饰符,则 int 参数首先转换为无符号字符。然后,如果在宽流函数中使用,则将字符转换为相应的宽字符。“-”标志可用于指定字段中的左对齐,但没有定义其他标志,也不能给出精度或类型修饰符。例如:

printf ("%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o');

打印 ‘hello’。

如果存在“l”修饰符,则该参数应为 wint_t 类型。如果在多字节函数中使用,则宽字符在添加到输出之前会转换为多字节字符。在这种情况下,可以产生一个以上的输出字节。

“%s”转换打印一个字符串。如果不存在“l”修饰符,则相应的参数必须是 char *(或 const char *)类型。如果在宽流函数中使用,则首先将字符串转换为宽字符串。可以指定精度来指示要写入的最大字符数;否则字符串中直到但不包括终止空字符的字符被写入输出流。“-”标志可用于在字段中指定左对齐,但没有为此转换定义其他标志或类型修饰符。例如:

printf ("%3s%-6s", "no", "where");

打印’ nowhere '。

如果存在“l”修饰符,则该参数应为 wchar_t 类型(或 const wchar_t *)。

如果您不小心将空指针作为“%s”转换的参数传递,GNU C 库会将其打印为“(null)”。我们认为这比崩溃更有用。但是故意传递一个空参数并不是一个好习惯。

‘%m’ 转换打印与 errno 中的错误代码对应的字符串。请参阅错误消息。因此:

fprintf (stderr, "can't open `%s': %m\n", filename);

相当于:

fprintf (stderr, "can't open `%s': %s\n", filename, strerror (errno));

‘%m’ 转换可以与’#’ 标志一起使用以打印错误常量,如 strerrorname_np 提供的那样。“%m”和“%#m”都是 GNU C 库扩展。

“%p”转换打印一个指针值。对应的参数必须是 void * 类型。在实践中,您可以使用任何类型的指针。

在 GNU C 库中,非空指针被打印为无符号整数,就像使用了“%#x”转换一样。空指针打印为’(nil)'。(指针在其他系统中的打印方式可能不同。)

例如:

printf ("%p", "testing");

打印’0x’后跟一个十六进制数——字符串常量“testing”的地址。它不会打印“testing”一词。

您可以提供带有“%p”转换的“-”标志来指定左对齐,但没有定义其他标志、精度或类型修饰符。

“%n”转换不同于任何其他输出转换。它使用一个参数,该参数必须是一个指向 int 的指针,但它不打印任何内容,而是存储到目前为止此调用在该位置打印的字符数。允许使用“h”和“l”类型修饰符来指定参数的类型为 short int * 或 long int * 而不是 int *,但不允许使用标志、字段宽度或精度。

例如,

int nchar;
printf ("%d %s%n\n", 3, "bears", &nchar);

打印:

3 bears

并将 nchar 设置为 7,因为“3 个熊”是七个字符。

‘%%’ 转换打印一个文字 ‘%’ 字符。此转换不使用参数,并且不允许使用标志、字段宽度、精度或类型修饰符。

2.12.7. 格式化输出函数

Formatted Output Functions

本节介绍如何调用 printf 和相关函数。这些函数的原型在头文件 stdio.h 中。由于这些函数采用可变数量的参数,因此您必须在使用它们之前为它们声明原型。当然,确保您拥有所有正确原型的最简单方法是仅包含 stdio.h。

函数:int printf (const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

printf 函数将模板字符串模板控制下的可选参数打印到流标准输出。它返回打印的字符数,如果有输出错误,则返回负值。

函数:int wprintf (const wchar_t *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

wprintf 函数将宽模板字符串模板控制下的可选参数打印到流标准输出。它返回打印的宽字符数,如果有输出错误,则返回负值。

函数:int fprintf (FILE *stream, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这个函数就像 printf 一样,只是输出被写入流流而不是标准输出。

函数:int fwprintf (FILE *stream, const wchar_t *template, ...)
Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这个函数和 wprintf 一样,只是输出被写入流流而不是标准输出。

函数:int sprintf (char *s, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这类似于 printf,只是输出存储在字符数组 s 中,而不是写入流中。写入空字符以标记字符串的结尾。

sprintf 函数返回存储在数组 s 中的字符数,不包括终止空字符。

如果复制发生在重叠的对象之间,则此函数的行为是未定义的——例如,如果 s 也作为要在“%s”转换控制下打印的参数给出。请参阅复制字符串和数组。

警告: sprintf 函数可能很危险,因为它可能会输出超出字符串 s 分配大小的字符。请记住,转换规范中给出的字段宽度只是最小值。

为避免此问题,您可以使用 snprintf 或 asprintf,如下所述。

函数:int swprintf (wchar_t *ws, size_t size, const wchar_t *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这与 wprintf 类似,只是输出存储在宽字符数组 ws 中,而不是写入流中。写入空宽字符以标记字符串的结尾。size 参数指定要生成的最大字符数。尾随空字符计入此限制,因此您应该为字符串 ws 分配至少 size 宽的字符。

返回值是为给定输入生成的字符数,不包括尾随的 null。如果不是所有输出都适合提供的缓冲区,则返回负值。您应该使用更大的输出字符串再试一次。注意:这与 snprintf 处理这种情况的方式不同。

请注意,相应的窄流函数采用较少的参数。swprintf 实际上对应于 snprintf 函数。由于 sprintf 函数可能很危险并且应该避免,因此 ISO C 委员会拒绝再次犯同样的错误,并决定不定义与 sprintf 完全对应的函数。

函数:int snprintf (char *s, size_t size, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

snprintf 函数与 sprintf 类似,不同之处在于 size 参数指定要生成的最大字符数。尾随空字符计入此限制,因此您应该为字符串 s 分配至少 size 个字符。如果 size 为零,则不应写入任何内容,甚至不写入空字节,并且 s 可能是空指针。

返回值是为给定输入生成的字符数,不包括尾随的空值。如果此值大于或等于 size,则并非结果中的所有字符都已存储在 s 中。您应该使用更大的输出字符串再试一次。这是执行此操作的示例:

/* Construct a message describing the value of a variablewhose name is name and whose value is value. */
char *
make_message (char *name, char *value)
{/* Guess we need no more than 100 bytes of space. */size_t size = 100;char *buffer = xmalloc (size);/* Try to print in the allocated space. */int buflen = snprintf (buffer, size, "value of %s is %s",name, value);if (! (0 <= buflen && buflen < SIZE_MAX))fatal ("integer overflow");if (buflen >= size){/* Reallocate buffer now that we knowhow much space is needed. */size = buflen;size++;buffer = xrealloc (buffer, size);/* Try again. */snprintf (buffer, size, "value of %s is %s",name, value);}/* The last call worked, return the string. */return buffer;
}

在实践中,使用 asprintf 通常更容易,如下所示。

注意:在 GNU C 库 2.1 之前的版本中,返回值是存储的字符数,不包括终止的 null; 除非 s 中没有足够的空间来存储结果,在这种情况下返回 -1。为了符合 ISO C99 标准,对此进行了更改。

2.12.8. 动态分配格式化输出

Dynamically Allocating Formatted Output

本节中的函数进行格式化输出并将结果放入动态分配的内存中。

函数:int asprintf (char **ptr, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

此函数与 sprintf 类似,不同之处在于它动态分配一个字符串(与 malloc 一样;请参阅无约束分配)来保存输出,而不是将输出放入您预先分配的缓冲区中。ptr 参数应该是 char * 对象的地址,并且成功调用 asprintf 会存储一个指向该位置新分配的字符串的指针。

返回值是分配给缓冲区的字符数,如果发生错误,返回值小于零。通常这意味着无法分配缓冲区。

以下是如何使用 asprintf 获得与 snprintf 示例相同的结果,但更容易:

/* Construct a message describing the value of a variablewhose name is name and whose value is value. */
char *
make_message (char *name, char *value)
{char *result;if (asprintf (&result, "value of %s is %s", name, value) < 0)return NULL;return result;
}

函数:int obstack_printf (struct obstack *obstack, const char *template, ...)

Preliminary: | MT-Safe race:obstack locale | AS-Unsafe corrupt heap | AC-Unsafe corrupt mem | See POSIX Safety Concepts.

这个函数和 asprintf 类似,只是它使用 obstack obstack 来分配空间。请参阅Obstacks。

字符被写入当前对象的末尾。要获得它们,您必须使用 obstack_finish 完成对象(请参阅Growing Objects)。

2.12.9. 变量参数输出函数

Variable Arguments Output Functions

提供了函数 vprintf 和朋友,以便您可以定义自己的可变参数 printf 函数,这些函数使用与内置格式化输出函数相同的内部结构。

定义此类函数最自然的方法是使用一种语言结构来表示:“调用 printf 并传递此模板以及前五个之后的所有参数。”但是在 C 中没有办法做到这一点,而且很难提供一种方法,因为在 C 语言级别没有办法告诉你的函数接收了多少参数。

由于这种方法是不可能的,我们提供了替代函数,即 vprintf 系列,它允许您传递一个 va_list 来描述“我在前五个之后的所有参数”。

当定义一个宏而不是一个真正的函数就足够时,GNU C 编译器提供了一种使用宏更容易做到这一点的方法。例如:

#define myprintf(a, b, c, d, e, rest...) \printf (mytemplate , ## rest)

有关详细信息,请参阅 C 预处理器中的可变参数宏。但这仅限于宏,根本不适用于实际函数。

在调用 vprintf 或本节中列出的其他函数之前,您必须调用 va_start(请参阅可变参数函数)来初始化指向变量参数的指针。然后你可以调用 va_arg 来获取你想要自己处理的参数。这会使指针超过这些参数。

一旦您的 va_list 指针指向您选择的参数,您就可以调用 vprintf。vprintf 使用该参数和传递给您的函数的所有后续参数以及您单独指定的模板。

可移植性注意:调用vprintf后va_list指针的值是不确定的,所以调用vprintf后一定不能使用va_arg。相反,您应该调用 va_end 从服务中退出指针。您可以再次调用 va_start 并开始从变量参数列表的开头获取参数。(或者,您可以在调用 vfprintf 之前使用 va_copy 制作 va_list 指针的副本。)调用 vprintf 不会破坏函数的参数列表,只会破坏您传递给它的特定指针。

这些函数的原型在 stdio.h 中声明。

函数:int vprintf (const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

此函数与 printf 类似,不同之处在于它不直接采用可变数量的参数,而是采用参数列表指针 ap。

函数:int vwprintf (const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这个函数与 wprintf 类似,只是它不是直接接受可变数量的参数,而是接受一个参数列表指针 ap。

函数:int vfprintf (FILE *stream, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这等效于 fprintf,其中变量参数列表直接指定为 vprintf。

函数:int vfwprintf (FILE *stream, const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这等效于 fwprintf,其中变量参数列表直接指定为 vwprintf。

函数:int vsprintf (char *s, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这等效于 sprintf,其中变量参数列表直接指定为 vprintf。

函数:int vswprintf (wchar_t *ws, size_t size, const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这等效于 swprintf,其中变量参数列表直接指定为 vwprintf。

函数:int vsnprintf (char *s, size_t size, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这等效于 snprintf,其中变量参数列表直接指定为 vprintf。

函数:int vasprintf (char **ptr, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

vasprintf 函数等效于 asprintf,其变量参数列表直接指定为 vprintf。

函数:int obstack_vprintf (struct obstack *obstack, const char *template, va_​​list ap)

Preliminary: | MT-Safe race:obstack locale | AS-Unsafe corrupt heap | AC-Unsafe corrupt mem | See POSIX Safety Concepts.

obstack_vprintf 函数等价于 obstack_printf,其变量参数列表直接指定为 vprintf。

这是一个示例,展示了如何使用 vfprintf。这是一个将错误消息打印到流 stderr 的函数,以及指示程序名称的前缀(有关 program_invocation_short_name 的描述,请参见错误消息)。

#include <stdio.h>
#include <stdarg.h>void
eprintf (const char *template, ...)
{va_list ap;extern char *program_invocation_short_name;fprintf (stderr, "%s: ", program_invocation_short_name);va_start (ap, template);vfprintf (stderr, template, ap);va_end (ap);
}

你可以这样调用 eprintf :

eprintf ("file `%s' does not exist\n", filename);

在 GNU C 中,您可以使用一种特殊的构造来让编译器知道函数使用 printf 样式的格式字符串。然后它可以检查每次调用函数的参数的数量和类型,并在它们与格式字符串不匹配时警告您。例如,以 eprintf 的这个声明为例:

void eprintf (const char *template, ...)__attribute__ ((format (printf, 1, 2)));

这告诉编译器 eprintf 使用像 printf 这样的格式字符串(而不是 scanf;请参阅格式化输入); 格式字符串作为第一个参数出现; 并且满足格式的参数从第二个开始。有关更多信息,请参阅使用 GNU CC 中声明函数的属性。

2.12.10. 解析模板字符串

Parsing a Template String

您可以使用函数 parse_printf_format 来获取有关给定模板字符串预期的参数的数量和类型的信息。此函数允许为 printf 提供接口的解释器避免传递来自用户程序的无效参数,这可能导致崩溃。

本节中描述的所有符号都在头文件 printf.h 中声明。

函数:size_t parse_printf_format (const char *template, size_t n, int *argtypes)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数返回有关 printf 模板字符串模板所期望的参数的数量和类型的信息。信息存储在数组 argtypes 中;该数组的每个元素描述一个参数。此信息使用下面列出的各种“PA_”宏进行编码。

参数 n 指定数组 argtypes 中的元素数。这是 parse_printf_format 将尝试写入的最大元素数。

parse_printf_format 返回模板所需的参数总数。如果此数字大于 n,则返回的信息仅描述前 n 个参数。如果您需要有关其他参数的信息,请分配一个更大的数组并再次调用 parse_printf_format。

参数类型被编码为基本类型和修饰标志位的组合。

宏:int PA_FLAG_MASK

此宏是类型修饰符标志位的位掩码。您可以编写表达式 (argtypes[i] & PA_FLAG_MASK) 仅提取参数的标志位,或 (argtypes[i] & ~PA_FLAG_MASK) 仅提取基本类型代码。

这里是代表基本类型的符号常量;它们代表整数值。

PA_INT

这指定基本类型是 int。

PA_CHAR

这指定基本类型是 int,转换为 char。

PA_STRING

这指定基本类型是 char *,一个以 null 结尾的字符串。

PA_POINTER

这指定基本类型是 void *,一个任意指针。

PA_FLOAT

这指定基本类型是浮点数。

PA_DOUBLE

这指定基本类型是双精度。

PA_LAST

您可以为自己的程序定义其他基本类型作为 PA_LAST 的偏移量。例如,如果您有数据类型“foo”和“bar”以及它们自己的专用 printf 转换,则可以将这些类型的编码定义为:

#define PA_FOO PA_LAST
#define PA_BAR (PA_LAST + 1)

以下是修改基本类型的标志位。它们使用 inclusive-or 与基本类型的代码相结合。

PA_FLAG_PTR

如果设置了该位,则表示编码类型是指向基本类型的指针,而不是立即值。例如,‘PA_INT|PA_FLAG_PTR’表示类型’int *’。

PA_FLAG_SHORT

如果该位被设置,则表示基类型被修改为short。(这对应于“h”类型修饰符。)

PA_FLAG_LONG

如果该位被设置,则表示基类型被修改为long。(这对应于“l”类型修饰符。)

PA_FLAG_LONG_LONG

如果该位被设置,则表示基类型被修改为 long long。(这对应于“L”类型修饰符。)

PA_FLAG_LONG_DOUBLE

这是 PA_FLAG_LONG_LONG 的同义词,按照约定与基本类型 PA_DOUBLE 一起使用来表示 long double 类型。

2.12.11. 解析模板字符串的例子

Example of Parsing a Template String

这是解码格式字符串的参数类型的示例。我们假设这是解释器的一部分,它包含类型为 NUMBER、CHAR、STRING 和 STRUCTURE 的参数(可能还有其他在这里无效的参数)。

/* Test whether the nargs specified objectsin the vector args are validfor the format string format:if so, return 1.If not, return 0 after printing an error message.  */int
validate_args (char *format, int nargs, OBJECT *args)
{int *argtypes;int nwanted;/* Get the information about the arguments.Each conversion specification must be at least two characterslong, so there cannot be more specifications than half thelength of the string.  */argtypes = (int *) alloca (strlen (format) / 2 * sizeof (int));nwanted = parse_printf_format (format, nargs, argtypes);/* Check the number of arguments.  */if (nwanted > nargs){error ("too few arguments (at least %d required)", nwanted);return 0;}/* Check the C type wanted for each argumentand see if the object given is suitable.  */for (i = 0; i < nwanted; i++){int wanted;if (argtypes[i] & PA_FLAG_PTR)wanted = STRUCTURE;elseswitch (argtypes[i] & ~PA_FLAG_MASK){case PA_INT:case PA_FLOAT:case PA_DOUBLE:wanted = NUMBER;break;case PA_CHAR:wanted = CHAR;break;case PA_STRING:wanted = STRING;break;case PA_POINTER:wanted = STRUCTURE;break;}if (TYPE (args[i]) != wanted){error ("type mismatch for arg number %d", i);return 0;}}return 1;
}

2.13. 自定义 printf

Customizing printf

GNU C 库允许您为 printf 模板字符串定义自己的自定义转换说明符,以教 printf 巧妙的方法来打印程序的重要数据结构。

这样做的方法是使用函数 register_printf_function 注册转换;请参阅注册新转换。您传递给此函数的参数之一是指向产生实际输出的处理函数的指针;有关如何编写此函数的信息,请参阅定义输出处理程序。

您还可以安装一个函数,该函数仅返回有关转换说明符预期的参数数量和类型的信息。有关这方面的信息,请参阅解析模板字符串。

本节的功能在头文件 printf.h 中声明。

可移植性说明:扩展 printf 模板字符串语法的能力是 GNU 扩展。ISO标准C没有类似的东西。当使用 GNU C 编译器或任何其他根据语言标准规则解释对标准 I/O 函数的调用的编译器时,有必要通过适当的编译器选项禁用此类处理。否则,依赖于扩展的程序的行为是未定义的。

2.13.1. 注册新转换

Registering New Conversions

注册新输出转换的函数是 register_printf_function,在 printf.h 中声明。

函数:int register_printf_function (int spec, printf_function handler-function, printf_arginfo_function arginfo-function)

Preliminary: | MT-Unsafe const:printfext | AS-Unsafe heap lock | AC-Unsafe mem lock | See POSIX Safety Concepts.

此函数定义转换说明符字符规范。因此,如果 spec 是“Y”,它定义了转换“%Y”。您可以重新定义像“%s”这样的内置转换,但是像“#”这样的标志字符和像“l”这样的类型修饰符永远不能用作转换;为这些字符调用 register_printf_function 无效。建议不要使用小写字母,因为 ISO C 标准警告说,在该标准的未来版本中可能会标准化额外的小写字母。

handler-function 是 printf 和朋友在此转换出现在模板字符串中时调用的函数。有关如何定义要作为此参数传递的函数的信息,请参阅定义输出处理程序。如果您指定空指针,则删除任何现有的 spec 处理函数。

arginfo-function 是 parse_printf_format 在此转换出现在模板字符串中时调用的函数。有关这方面的信息,请参阅解析模板字符串。

注意:在 2.0 之前的 GNU C 库版本中,不需要安装 arginfo-function 函数,除非用户使用 parse_printf_format 函数。这已经改变了。现在,当格式说明符出现在格式字符串中时,对任何 printf 函数的调用都将调用此函数。

成功时返回值为 0,失败时返回值为 -1(如果规范超出范围,则会发生这种情况)。

可移植性注意:可以重新定义标准输出转换,但强烈建议不要这样做,因为它可能会干扰假定转换效果符合相关语言标准的程序和编译器实现的行为。此外,符合标准的编译器不需要保证为标准转换注册的函数将在程序中的每个格式字符串中为每个此类转换调用。

2.13.2. 转换说明符选项

Conversion Specifier Options

如果为’%A’定义一个含义,如果模板包含’%+23A’或’%-#A’怎么办?为了实现这些的合理含义,处理程序在被调用时需要能够获取模板中指定的选项。

handler-function 和 arginfo-function 都接受指向结构体 printf_info 的参数,该结构体包含有关出现在转换说明符实例中的选项的信息。此数据类型在头文件 printf.h 中声明。

类型:struct printf_info

此结构用于将有关出现在 printf 模板字符串中的转换说明符实例中的选项的信息传递给该说明符的处理程序和 arginfo 函数。它包含以下成员:

int prec

这是指定的精度。如果未指定精度,则值为 -1。如果精度为“*”,则传递给处理函数的 printf_info 结构包含从参数列表中检索到的实际值。但是传递给 arginfo 函数的结构包含一个 INT_MIN 值,因为实际值未知。

int width

这是指定的最小字段宽度。如果未指定宽度,则值为 0。如果字段宽度为“*”,则传递给处理函数的 printf_info 结构包含从参数列表中检索到的实际值。但是传递给 arginfo 函数的结构包含一个 INT_MIN 值,因为实际值未知。

wchar_t spec

这是指定的转换说明符字符。它存储在结构中,以便您可以为多个字符注册相同的处理函数,但仍然有办法在调用处理函数时将它们区分开来。

unsigned int is_long_double

如果指定了“L”、“ll”或“q”类型修饰符,则这是一个布尔值。对于整数转换,这表示 long long int,与浮点转换的 long double 相反。

unsigned int is_char

如果指定了“hh”类型修饰符,则这是一个布尔值。

unsigned int is_short

如果指定了“h”类型修饰符,则这是一个布尔值。

unsigned int is_long

如果指定了“l”类型修饰符,则这是一个布尔值。

unsigned int alt

如果指定了“#”标志,则这是一个布尔值。

unsigned int space

如果指定了“ ”标志,则这是一个布尔值。

unsigned int left

如果指定了“-”标志,则这是一个布尔值。

unsigned int showsign

如果指定了“+”标志,这是一个布尔值。

unsigned int group

如果指定了’''标志,这是一个布尔值。

unsigned int extra

该标志根据上下文具有特殊含义。它可以由用户定义的处理程序自由使用,但是当从 printf 函数调用时,此变量始终包含值 0。

unsigned int wide

如果流是宽向的,则设置此标志。

wchar_t pad

这是用于将输出填充到最小字段宽度的字符。如果指定了“0”标志,则值为“0”,否则为“”。

2.13.3. 定义输出处理程序

Defining the Output Handler

现在让我们看看如何定义作为参数传递给 register_printf_function 的处理程序和 arginfo 函数。

兼容性说明: GNU C 库 2.0 版中的接口已更改。以前第三个参数的类型是 va_list *。

您应该使用如下原型定义处理程序函数:

int function (FILE *stream, const struct printf_info *info,const void *const *args)

传递给处理函数的流参数是它应该写入输出的流。

info 参数是一个指向结构的指针,该结构包含有关模板字符串中的转换所包含的各种选项的信息。您不应在处理程序函数中修改此结构。有关此数据结构的说明,请参见转换说明符选项。

args 是指向参数数据的指针向量。参数的数量是通过调用用户提供的参数信息函数来确定的。

你的处理函数应该像 printf 一样返回一个值:它应该返回它写入的字符数,或者一个负值表示错误。

数据类型:printf_function

这是处理函数应具有的数据类型。

如果要在应用程序中使用 parse_printf_format,则还必须定义一个函数,作为使用 register_printf_function 安装的每个新转换的 arginfo-function 参数传递。

您必须使用如下原型定义这些函数:

int function (const struct printf_info *info,size_t n, int *argtypes)

函数的返回值应该是转换期望的参数数量。该函数还应在 argtypes 数组的不超过 n 个元素中填充有关每个参数的类型的信息。此信息使用各种“PA_”宏进行编码。(您会注意到这与 parse_printf_format 本身使用的调用约定相同。)

数据类型:printf_arginfo_function

此类型用于描述返回有关转换说明符使用的参数的数量和类型的信息的函数。

2.13.4. printf 扩展示例

printf Extension Example

这是一个显示如何定义 printf 处理函数的示例。该程序定义了一个称为 Widget 的数据结构,并定义了“%W”转换以打印有关 Widget * 参数的信息,包括指针值和存储在数据结构中的名称。“%W”转换支持最小字段宽度和左对齐选项,但忽略其他所有内容。

#include <stdio.h>
#include <stdlib.h>
#include <printf.h>typedef struct
{char *name;
}
Widget;int
print_widget (FILE *stream,const struct printf_info *info,const void *const *args)
{const Widget *w;char *buffer;int len;/* Format the output into a string. */w = *((const Widget **) (args[0]));len = asprintf (&buffer, "<Widget %p: %s>", w, w->name);if (len == -1)return -1;/* Pad to the minimum field width and print to the stream. */len = fprintf (stream, "%*s",(info->left ? -info->width : info->width),buffer);/* Clean up and return. */free (buffer);return len;
}int
print_widget_arginfo (const struct printf_info *info, size_t n,int *argtypes)
{/* We always take exactly one argument and this is a pointer to thestructure.. */if (n > 0)argtypes[0] = PA_POINTER;return 1;
}int
main (void)
{/* Make a widget to print. */Widget mywidget;mywidget.name = "mywidget";/* Register the print function for widgets. */register_printf_function ('W', print_widget, print_widget_arginfo);/* Now print the widget. */printf ("|%W|\n", &mywidget);printf ("|%35W|\n", &mywidget);printf ("|%-35W|\n", &mywidget);return 0;
}

该程序产生的输出如下所示:

|<Widget 0xffeffb7c: mywidget>|
|      <Widget 0xffeffb7c: mywidget>|
|<Widget 0xffeffb7c: mywidget>      |

2.13.5. 预定义的 printf 处理程序

Predefined printf Handlers

GNU C 库还包含 printf 处理程序扩展的具体且有用的应用程序。有两个函数可以实现打印浮点数的特殊方式。

函数:int printf_size (FILE *fp, const struct printf_info *info, const void *const *args)

Preliminary: | MT-Safe race:fp locale | AS-Unsafe corrupt heap | AC-Unsafe mem corrupt | See POSIX Safety Concepts.

以 %f 格式打印给定的浮点数,除了有一个后缀字符指示该数字的除数以使其小于 1000。有两种可能的除数:1024 的幂或 1000 的幂。使用哪一个取决于注册此处理程序时指定的格式字符。如果字符是小写,则使用 1024。对于大写字符,使用 1000。

后缀标签对应字节、千字节、兆字节、千兆字节等,全表为:

默认精度为 3,即 1024 以小写格式字符打印,就好像它是 %.3fk 一样,将产生 1.000k。

由于 register_printf_function 的要求,我们还必须提供返回参数信息的函数。

函数:int printf_size_info (const struct printf_info *info, size_t n, int *argtypes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将以 argtypes 的形式返回有关 vfprintf 实现所期望的所用参数的信息。格式始终采用一个参数。

要使用这些函数,这两个函数都必须注册一个调用,例如

register_printf_function ('B', printf_size, printf_size_info);

在这里,我们注册了将数字打印为 1000 的幂的函数,因为格式字符“B”是大写字符。如果我们在一行中额外使用 ‘b’

register_printf_function ('b', printf_size, printf_size_info);

我们也可以使用 1024 的幂进行打印。请注意,这两行的不同之处在于格式说明符。printf_size 函数知道大小写格式说明符之间的区别。

“B”和“b”的使用并非巧合。相反,它是使用此功能的首选方式,因为它在其他一些也使用格式说明符的系统上可用。

2.14. 格式化输入

Formatted Input

本节中描述的函数(scanf 和相关函数)为格式化输入提供了类似于格式化输出工具的工具。这些函数提供了一种在格式字符串或模板字符串的控制下读取任意值的机制。

2.14.1. 格式化输入基础

Formatted Input Basics

对 scanf 的调用表面上类似于对 printf 的调用,因为任意参数是在模板字符串的控制下读取的。虽然模板中转换规范的语法与 printf 的语法非常相似,但模板的解释更倾向于自由格式输入和简单的模式匹配,而不是固定字段格式。例如,大多数 scanf 转换会跳过输入文件中任何数量的“空白”(包括空格、制表符和换行符),并且没有数字输入转换的精度概念,因为相应的输出转换没有精度概念。通常,模板中的非空白字符应与输入流中的字符完全匹配,但匹配失败与流上的输入错误不同。

scanf 和 printf 之间的另一个区别是您必须记住提供指针而不是立即值作为 scanf 的可选参数。读取的值存储在指针指向的对象中。即使是有经验的程序员也会偶尔忘记这一点,因此如果您的程序出现似乎与 scanf 相关的奇怪错误,您可能需要仔细检查这一点。

当匹配失败时,scanf 立即返回,将第一个不匹配的字符作为下一个要从流中读取的字符。scanf 的正常返回值是分配的值的数量,因此您可以使用它来确定在读取所有预期值之前是否发生匹配错误。

scanf 函数通常用于读取表格内容等。例如,这是一个使用 scanf 初始化 double 数组的函数:

void
readarray (double *array, int n)
{int i;for (i=0; i<n; i++)if (scanf (" %lf", &(array[i])) != 1)invalid_input_error ();
}

格式化输入函数不像格式化输出函数那样频繁使用。部分原因是因为正确使用它们需要一些小心。另一个原因是很难从匹配错误中恢复。

如果您尝试读取与单个固定模式不匹配的输入,则最好使用 Flex 等工具生成词法扫描器,或使用 Bison 生成解析器,而不是使用 scanf。有关这些工具的更多信息,请参阅 Flex:词法扫描器生成器和 Bison 参考手册。

2.14.2. 输入转换语法

Input Conversion Syntax

scanf 模板字符串是一个包含普通多字节字符的字符串,其中散布着以“%”开头的转换规范。

模板中的任何空白字符(由 isspace 函数定义;请参阅字符分类)会导致输入流中的任意数量的空白字符被读取和丢弃。匹配的空白字符不必与模板字符串中出现的空白字符完全相同。例如,在模板中写入“,”以识别逗号前后可选空格。

模板字符串中不属于转换规范的其他字符必须与输入流中的字符完全匹配;如果不是这种情况,则会发生匹配失败。

scanf 模板字符串中的转换规范具有一般形式:

% flags width type conversion

更详细地说,输入转换规范由一个初始的“%”字符组成,后面依次是:

  • 一个可选的标志字符“*”,表示忽略为本规范读取的文本。当 scanf 找到使用此标志的转换规范时,它会按照转换规范的其余部分的指示读取输入,但它会丢弃此输入,不使用指针参数,并且不增加成功分配的计数。
    一- 个可选的标志字符“a”(仅对字符串转换有效),它请求分配足够长的缓冲区以存储字符串。(这是 GNU 扩展。)请参阅动态分配字符串转换。
  • 一个可选的十进制整数,指定最大字段宽度。当达到此最大值或找到不匹配的字符时(以先发生者为准),停止从输入流中读取字符。大多数转换会丢弃初始空白字符(那些没有明确记录的字符),并且这些丢弃的字符不计入最大字段宽度。字符串输入转换存储- 一个空字符来标记输入的结束;最大字段宽度不包括此终止符。
  • 可选的类型修饰符。例如,您可以使用“%d”等整数转换指定类型修饰符“l”,以指定参数是指向 long int 的指针,而不是指向 int 的指针。
  • 指定要应用的转换的字符。

允许的确切选项以及它们的解释方式因不同的转换说明符而异。有关它们允许的特定选项的信息,请参阅各个转换的描述。

使用“-Wformat”选项,GNU C 编译器检查对 scanf 和相关函数的调用。它检查格式字符串并验证是否提供了正确数量和类型的参数。还有一种 GNU C 语法告诉编译器您编写的函数使用 scanf 样式的格式字符串。有关更多信息,请参阅使用 GNU CC 中声明函数的属性。

2.14.3. 输入转换表

Table of Input Conversions

下表总结了各种转换规范:

‘%d’

匹配以十进制形式写入的可选带符号整数。请参阅数字输入转换。

‘%i’

匹配 C 语言为指定整数常量而定义的任何格式的可选带符号整数。请参阅数字输入转换。

‘%o’

匹配以八进制基数编写的无符号整数。请参阅数字输入转换。

‘%u’

匹配以十进制基数编写的无符号整数。请参阅数字输入转换。

‘%x’, ‘%X’

匹配以十六进制基数编写的无符号整数。请参阅数字输入转换。

‘%e’、‘%f’、‘%g’、‘%E’、‘%G’

匹配可选带符号的浮点数。请参阅数字输入转换。

‘%s’

匹配仅包含非空白字符的字符串。请参阅字符串输入转换。‘l’ 修饰符的存在决定了输出是存储为宽字符串还是多字节字符串。如果在宽字符函数中使用了“%s”,则字符串将与多次调用 wcrtomb 一样转换为多字节字符串。这意味着缓冲区必须为每个读取的宽字符提供 MB_CUR_MAX 字节空间。如果在多字节函数中使用了“%ls”,则结果将被转换为宽字符,就像多次调用 mbrtowc 一样,然后再存储在用户提供的缓冲区中。

‘%S’

这是“%ls”的别名,支持与 Unix 标准兼容。

‘%[’

匹配属于指定集合的​​字符串。请参阅字符串输入转换。‘l’ 修饰符的存在决定了输出是存储为宽字符串还是多字节字符串。如果在宽字符函数中使用“%[”,则字符串将与多次调用 wcrtomb 一样转换为多字节字符串。这意味着缓冲区必须为每个读取的宽字符提供 MB_CUR_MAX 字节空间。如果在多字节函数中使用“%l[”,则结果将转换为宽字符,就像多次调用 mbrtowc 一样,然后存储在用户提供的缓冲区中。

‘%c’

匹配一个或多个字符的字符串;读取的字符数由为转换指定的最大字段宽度控制。请参阅字符串输入转换。

如果在宽流函数中使用“%c”,则读取值在存储之前从宽字符转换为相应的多字节字符。请注意,这种转换可以产生多于一个字节的输出,因此提供的缓冲区必须足够大,每个字符最多可以容纳 MB_CUR_MAX 个字节。如果在多字节函数中使用了“%lc”,则输入被视为多字节序列(而不是字节),并且结果将被转换为调用 mbrtowc。

‘%C’

这是“%lc”的别名,支持与 Unix 标准兼容。

‘%p’

与 printf 的“%p”输出转换所使用的实现定义格式相同的指针值匹配。请参阅其他输入转换。

‘%n’

此转换不读取任何字符;它记录了到目前为止此调用读取的字符数。请参阅其他输入转换。

‘%%’

这匹配输入流中的文字“%”字符。没有使用相应的参数。请参阅其他输入转换。

如果转换规范的语法无效,则行为未定义。如果没有提供足够的函数参数来为执行赋值的模板字符串中的所有转换规范提供地址,或者如果参数的类型不正确,则行为也是未定义的。另一方面,额外的参数被简单地忽略。

2.14.4. 数字输入转换

Numeric Input Conversions

本节介绍用于读取数值的 scanf 转换。

‘%d’ 转换匹配一个可选带符号的十进制基数整数。识别的语法与 strtol 函数(请参阅整数解析)的语法相同,基本参数的值为 10。

‘%i’ 转换匹配 C 语言为指定整数常量而定义的任何格式的可选带符号整数。所识别的语法与 strtol 函数(请参阅整数解析)的语法相同,其中 base 参数的值为 0。(您可以在此语法中使用 printf 打印整数,方法是使用带有“%x”、“%o”或“%d”转换的“#”标志字符。请参阅整数转换。)

例如,任何字符串“10”、“0xa”或“012”都可以在“%i”转换下作为整数读入。这些中的每一个都指定了一个十进制值为 10 的数字。

“%o”、“%u”和“%x”转换分别匹配八进制、十进制和十六进制基数的无符号整数。识别的语法与 strtoul 函数的语法相同(请参阅整数解析),基参数具有适当的值(8、10 或 16)。

“%X”转换与“%x”转换相同。它们都允许将大写或小写字母用作数字。

%d、%i 和 %n 转换的对应参数的默认类型是 int *,而其他整数转换的默认类型是 unsigned int *。您可以使用以下类型修饰符来指定其他大小的整数:

‘hh’

指定参数是有符号字符 * 或无符号字符 *。

此修饰符是在 ISO C99 中引入的。

‘h’

指定参数是一个短整型 * 或无符号短整型 *。

‘j’

指定参数是 intmax_t * 或 uintmax_t *。

此修饰符是在 ISO C99 中引入的。

‘l’

指定参数是 long int * 或 unsigned long int *。两个“l”字符就像下面的“L”修饰符。

如果与“%c”或“%s”一起使用,则相应的参数被分别视为指向宽字符或宽字符串的指针。“l”的这种用法是在 ISO C90 的修正案 1 中引入的。

'll'
'L'
'q'

指定参数是 long long int * 或 unsigned long long int *。(long long 类型是 GNU C 编译器支持的扩展。对于不提供超长整数的系统,这与 long int 相同。)

‘q’ 修饰符是同一事物的另一个名称,它来自 4.4 BSD; long long int 有时称为“quad” int。

‘t’

指定参数是 ptrdiff_t *。

此修饰符是在 ISO C99 中引入的。

‘z’

指定参数是 size_t *。

此修饰符是在 ISO C99 中引入的。

所有“%e”、“%f”、“%g”、“%E”和“%G”输入转换都是可互换的。它们都匹配可选带符号的浮点数,语法与 strtod 函数相同(请参阅浮点解析)。

对于浮点输入转换,默认参数类型是 float *。(这与相应的输出转换不​​同,其中默认类型为 double;请记住,printf 的 float 参数通过默认参数提升转换为 double,但 float * 参数不会提升为 double *。)您可以指定其他大小使用这些类型修饰符的浮点数:

‘l’

指定参数是双 * 类型。

‘L’

指定参数的类型为 long double *。

对于上述所有数字解析格式,还有一个额外的可选标志“”。当给定此标志时,scanf 函数期望输入字符串中表示的数字根据当前所选语言环境的分组规则进行格式化(请参阅通用数字格式参数)。

如果选择“C”或“POSIX”语言环境,则没有区别。但是对于为区域设置中的适当字段指定值的区域设置,输入必须在输入中具有正确的形式。否则处理具有正确形式的最长前缀。

2.14.5. 字符串输入转换

String Input Conversions

本节介绍用于读取字符串和字符值的 scanf 输入转换:“%s”、“%S”、“%[”、“%c”和“%C”。

对于如何从这些转换中接收输入,您有两种选择:

  • 提供一个缓冲区来存储它。这是默认设置。您应该提供 char * 或 wchar_t * 类型的参数(如果存在“l”修饰符,则为后者)。

    警告:要制作一个健壮的程序,您必须确保输入(加上它的终止 null)不可能超过您提供的缓冲区的大小。通常,这样做的唯一方法是指定最大字段宽度比缓冲区大小小一。如果您提供缓冲区,请始终指定最大字段宽度以防止溢出。

  • 通过指定“a”标志字符,让 scanf 分配足够大的缓冲区。这是一个 GNU 扩展。您应该为要存储的缓冲区地址提供 char ** 类型的参数。请参阅动态分配字符串转换。

‘%c’ 转换是最简单的:它总是匹配固定数量的字符。最大字段宽度表示要读取多少个字符;如果不指定最大值,则默认值为 1。此转换不会将空字符附加到它读取的文本的末尾。它也不会跳过初始空白字符。它会精确读取接下来的 n 个字符,如果无法读取那么多字符,则会失败。因为’%c’总是有一个最大字段宽度(无论是指定的,还是默认为 1),你总是可以通过使缓冲区足够长来防止溢出。

如果格式是“%lc”或“%C”,则函数存储使用从外部字节流打开流时确定的转换来转换的宽字符。从介质读取的字节数受 MB_CUR_LEN * n 限制,但最多 n 个宽字符存储在输出字符串中。

“%s”转换匹配一串非空白字符。它会跳过并丢弃初始空白,但在读取某些内容后遇到更多空白时会停止。它在读取的文本末尾存储一个空字符。

例如,读取输入:

 hello, world

使用转换“%10c”会产生“hello, wo”,但使用转换“%10s”读取相同的输入会产生“hello”。

警告:如果您没有为“%s”指定字段宽度,则读取的字符数仅受下一个空白字符出现位置的限制。这几乎可以肯定地意味着无效输入会使您的程序崩溃——这是一个错误。

“%ls”和“%S”格式的处理方式与“%s”类似,只是外部字节序列使用与流相关的转换转换为具有自己编码的宽字符。使用格式指定的宽度或精度不会直接确定从流中读取的字节数,因为它们测量的是宽字符。但是可以通过将宽度或精度的值乘以 MB_CUR_MAX 来计算上限。

要读入属于您选择的任意集合的字符,请使用“%[”转换。您可以使用正则表达式中用于显式字符集的相同语法来指定“[”字符和后面的“]”字符之间的集合。作为特殊情况:

  • 可以将文字“]”字符指定为集合的第一个字符。
  • 嵌入的“-”字符(即不是集合的第一个或最后一个字符)用于指定字符范围。
  • 如果插入字符“^”紧跟在最初的“[”之后,则允许输入的字符集是除了列出的字符之外的所有字符。

‘%[’ 转换不会跳过初始空白字符。

请注意,出现在正则表达式中的字符集中可用的字符类语法(例如“[:alpha:]”)在“%[”转换中不可用。

以下是“%[”转换的一些示例及其含义:

‘%25[1234567890]’

匹配最多 25 位数字的字符串。

‘%25[][]’

匹配最多 25 个方括号的字符串。

‘%25[^ \f\n\r\t\v]’

匹配不包含任何标准空白字符的最长 25 个字符的字符串。这与“%s”略有不同,因为如果输入以空格字符开头,“%[”会报告匹配失败,而“%s”会简单地丢弃初始空格。

‘%25[a-z]’

最多匹配 25 个小写字符。

至于’%c’和’%s’,如果’l’修饰符存在,'%['格式也被修改为产生宽字符。上面关于“%ls”的所有内容都适用于“%l[”。

再提醒一点:如果您不指定最大宽度或使用“a”标志,则“%s”和“%[”转换是危险的,因为输入太长会溢出您为其提供的任何缓冲区。无论您的缓冲区有多长,用户都可以提供更长的输入。一个编写良好的程序会通过可理解的错误消息报告无效输入,而不是崩溃。

2.14.6. 动态分配字符串转换

Dynamically Allocating String Conversions

格式化输入的 GNU 扩展使您可以安全地读取没有最大大小的字符串。使用此功能,您无需提供缓冲区; 相反,scanf 分配了一个足够大的缓冲区来保存数据并为您提供其地址。要使用此功能,请将“a”写为标志字符,如“%as”或“%a[0-9a-z]”。

您为存储输入的位置提供的指针参数应具有 char ** 类型。scanf 函数分配一个缓冲区并将其地址存储在参数指向的字中。当不再需要缓冲区时,应使用 free 释放缓冲区。

这是一个使用带有“%[…]”转换规范的“a”标志来读取“variable = value”形式的“变量赋值”的示例。

{char *variable, *value;if (2 > scanf ("%a[a-zA-Z0-9] = %a[^\n]\n",&variable, &value)){invalid_input_error ();return 0;}…
}

2.14.7. 其他输入转换

Other Input Conversions

本节介绍各种输入转换。

“%p”转换用于读取指针值。它识别 printf 的“%p”输出转换使用的相同语法(请参阅其他输出转换);也就是说,一个十六进制数,就像“%x”转换所接受的那样。对应的参数应该是 void ** 类型;也就是存放指针的地方的地址。

如果结果指针值不是在读取它的同一程序执行期间最初写入的,则不能保证它是有效的。

‘%n’ 转换产生到目前为止此调用读取的字符数。相应的参数应该是 int * 类型,除非类型修饰符有效(请参阅数字输入转换)。此转换的工作方式与 printf 的“%n”转换相同;例如,请参阅其他输出转换。

“%n”转换是确定文字匹配或使用抑制分配的转换是否成功的唯一机制。如果“%n”在匹配失败的轨迹之后,则不会为其存储任何值,因为 scanf 在处理“%n”之前返回。如果在调用 scanf 之前将 -1 存储在该参数槽中,则在 scanf 之后存在 -1 表示在到达“%n”之前发生了错误。

最后,“%%”转换匹配输入流中的文字“%”字符,而不使用参数。此转换不允许指定任何标志、字段宽度或类型修饰符。

2.14.8. 格式化输入函数

Formatted Input Functions

以下是执行格式化输入的函数的描述。这些函数的原型在头文件 stdio.h 中。

函数:int scanf (const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

scanf 函数在模板字符串模板的控制下从流标准输入读取格式化输入。可选参数是指向接收结果值的位置的指针。

返回值通常是成功分配的数量。如果在执行任何匹配之前检测到文件结束条件,包括与模板中的空格和文字字符的匹配,则返回 EOF。

函数:int wscanf (const wchar_t *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

wscanf 函数在模板字符串模板的控制下从流标准输入读取格式化输入。可选参数是指向接收结果值的位置的指针。

返回值通常是成功分配的数量。如果在执行任何匹配之前检测到文件结束条件,包括与模板中的空格和文字字符的匹配,则返回 WEOF。

函数:int fscanf (FILE *stream, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这个函数和 scanf 一样,只是输入是从流流而不是标准输入中读取的。

函数:int fwscanf (FILE *stream, const wchar_t *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这个函数和 wscanf 一样,只是输入是从流流而不是标准输入中读取的。

函数:int sscanf (const char *s, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这与 scanf 类似,只是字符取自以 null 结尾的字符串 s 而不是来自流。到达字符串的末尾被视为文件结束条件。

如果复制发生在重叠的对象之间,则此函数的行为是未定义的——例如,如果 s 也作为参数给出以接收在 ‘%s’、‘%S’ 或 ‘%[ 控制下读取的字符串’ 转换。

函数:int swscanf (const wchar_t *ws, const wchar_t *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这与 wscanf 类似,只是字符取自以空字符结尾的字符串 ws,而不是取自流。到达字符串的末尾被视为文件结束条件。

如果复制发生在重叠的对象之间,则此函数的行为是未定义的——例如,如果 ws 也作为参数给出以接收在 ‘%s’、‘%S’ 或 ‘%[ 控制下读取的字符串’ 转换。

2.14.9. 变量参数输入函数

Variable Arguments Input Functions

提供了函数 vscanf 和朋友,以便您可以定义自己的可变参数 scanf 函数,这些函数使用与内置格式化输出函数相同的内部结构。这些函数类似于 vprintf 系列的输出函数。有关如何使用它们的重要信息,请参阅变量参数输出函数。

可移植性注意:本节中列出的函数是在 ISO C99 中引入的,并且之前作为 GNU 扩展提供。

函数:int vscanf (const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

此函数类似于 scanf,但不是直接采用可变数量的参数,而是采用 va_list 类型的参数列表指针 ap(请参阅可变参数函数)。

函数:int vwscanf (const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

此函数类似于 wscanf,但它不是直接采用可变数量的参数,而是采用 va_list 类型的参数列表指针 ap(请参阅可变参数函数)。

函数:int vfscanf (FILE *stream, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这等效于 fscanf,其中变量参数列表直接指定为 vscanf。

函数:int vfwscanf (FILE *stream, const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

这等效于 fwscanf,其中变量参数列表直接指定为 vwscanf。

函数:int vsscanf (const char *s, const char *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这等效于 sscanf,其中变量参数列表直接指定为 vscanf。

函数:int vswscanf (const wchar_t *s, const wchar_t *template, va_​​list ap)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

这等效于 swscanf,其中变量参数列表直接指定为 vwscanf。

在 GNU C 中,您可以使用一个特殊的构造来让编译器知道函数使用的是 scanf 样式的格式字符串。然后它可以检查每次调用函数的参数的数量和类型,并在它们与格式字符串不匹配时警告您。有关详细信息,请参阅使用 GNU CC 中声明函数的属性。

2.15. 文件结束和错误

End-Of-File and Errors

本章中描述的许多函数都返回宏 EOF 的值来指示操作未成功完成。由于 EOF 用于报告文件结尾和随机错误,因此通常最好使用 feof 函数显式检查文件结尾并使用 ferror 来检查错误。这些函数检查作为流对象内部状态一部分的指标,如果该流上的先前 I/O 操作检测到适当的条件,则设置指标。

宏:int EOF

该宏是一个整数值,由许多窄流函数返回,以指示文件结束条件或其他一些错误情况。对于 GNU C 库,EOF 为 -1。在其他库中,它的值可能是其他负数。

该符号在 stdio.h 中声明。

宏:int WEOF

该宏是一个整数值,由许多宽流函数返回,以指示文件结束条件或其他一些错误情况。对于 GNU C 库,WEOF 为 -1。在其他库中,它的值可能是其他负数。

这个符号在 wchar.h 中声明。

函数:int feof (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

当且仅当设置了流流的文件结束指示符时,feof 函数才返回非零值。

该符号在 stdio.h 中声明。

函数:int feof_unlocked (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

feof_unlocked 函数等价于 feof 函数,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

该符号在 stdio.h 中声明。

函数:int ferror (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

当且仅当设置了流流的错误指示符时,ferror 函数才返回非零值,表明流上的先前操作发生了错误。

该符号在 stdio.h 中声明。

函数:int ferror_unlocked (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

ferror_unlocked 函数与 ferror 函数等效,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

该符号在 stdio.h 中声明。

除了设置与流相关的错误指示符之外,对流进行操作的函数也设置 errno,方法与对文件描述符进行操作的相应低级函数相同。例如,所有执行输出到流的函数(如 fputc、printf 和 fflush)都是根据 write 实现的,并且为 write 定义的所有 errno 错误条件对这些函数都是有意义的。有关描述符级别 I/O 函数的更多信息,请参阅底层输入/输出。

2.16. 从错误中恢复

Recovering from errors

您可以使用 clearerr 函数显式清除错误和 EOF 标志。

函数:void clearerr (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Unsafe lock | See POSIX Safety Concepts.

此函数清除流流的文件结尾和错误指示符。

文件定位功能(请参阅文件定位)还清除流的文件结束指示符。

函数:void clearerr_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Safe | AC-Safe | See POSIX Safety Concepts.

clearerr_unlocked 函数等效于 clearerr 函数,只是它不隐式锁定流。

这个函数是一个 GNU 扩展。

请注意,仅清除错误标志并重试失败的流操作是不正确的。写入失败后,自上次缓冲区刷新以来的任意数量的字符可能已提交到文件,而一些缓冲数据可能已被丢弃。因此,仅仅重试可能会导致数据丢失或重复。

读取失败可能会使文件指针处于不合适的位置以进行第二次尝试。在这两种情况下,您都应该在重试之前寻找一个已知位置。

大多数可能发生的错误都是不可恢复的——第二次尝试总是会以同样的方式再次失败。所以通常最好放弃并向用户报告错误,而不是安装复杂的恢复逻辑。

一个重要的例外是 EINTR(请参阅信号中断的原语)。许多流 I/O 实现会将其视为普通错误,这可能非常不方便。您可以通过安装所有带有 SA_RESTART 标志的信号来避免这种麻烦。

出于类似的原因,通常不建议在流的文件描述符上设置非阻塞 I/O。

2.17. 文本和二进制流

Text and Binary Streams

GNU 系统和其他与 POSIX 兼容的操作系统将所有文件组织为统一的字符序列。但是,其他一些系统对包含文本的文件和包含二进制数据的文件进行了区分,ISO C 的输入和输出工具提供了这种区分。本节将告诉您如何编写可移植到此类系统的程序。

打开流时,可以指定文本流或二进制流。您通过在 fopen 的 opentype 参数中指定“b”修饰符来表明您想要一个二进制流;请参阅打开流。如果没有此选项,fopen 将文件作为文本流打开。

文本流和二进制流在几个方面有所不同:

  • 从文本流中读取的数据分为以换行符 (‘\n’) 字符结尾的行,而二进制流只是一长串字符。某些系统上的文本流可能无法处理长度超过 254 个字符的行(包括终止换行符)。
  • 在某些系统上,文本文件只能包含打印字符、水平制表符和换行符,因此文本流可能不支持其他字符。但是,二进制流可以处理任何字符值。
  • 再次读入文件时,在文本流中的换行符之前写入的空格字符可能会消失。
  • 更一般地,从文本流读取或写入到文本流的字符与实际文件中的字符之间不需要一对一的映射。

由于二进制流总是比文本流更有能力和更可预测,您可能想知道文本流的用途是什么。为什么不总是简单地使用二进制流?答案是,在这些操作系统上,文本流和二进制流使用不同的文件格式,并且读取或写入可以与其他面向文本的程序一起使用的“普通文本文件”的唯一方法是通过文本流。

在 GNU C 库和所有 POSIX 系统中,文本流和二进制流之间没有区别。当您打开一个流时,无论您是否要求二进制,您都会得到相同类型的流。此流可以处理任何文件内容,并且没有文本流有时具有的限制。

2.18. 文件定位

File Positioning

流的文件位置描述了流当前在文件中读取或写入的位置。流上的 I/O 通过文件推进文件位置。在 GNU 系统上,文件位置表示为一个整数,它计算从文件开头开始的字节数。请参阅文件位置。

在对普通磁盘文件进行 I/O 操作时,您可以随时更改文件位置,从而读取或写入文件的任何部分。一些其他类型的文件也可能允许这样做。支持更改文件位置的文件有时称为随机访问文件。

您可以使用本节中的函数来检查或修改与流关联的文件位置指示符。下面列出的符号在头文件 stdio.h 中声明。

函数:long int ftell (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

该函数返回流流的当前文件位置。

如果流不支持文件定位,或者文件位置不能用 long int 表示,也可能由于其他原因,此函数可能会失败。如果发生故障,则返回值 -1。

函数:off_t ftello (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

ftello 函数与 ftell 类似,不同之处在于它返回一个 off_t 类型的值。支持这种类型的系统使用它来描述所有文件位置,这与使用 long int 的 POSIX 规范不同。两者的大小不一定相同。因此,如果实现是在符合 POSIX 的低级 I/O 实现之上编写的,则使用 ftell 可能会导致问题,并且只要可用,最好使用 ftello。

如果此函数失败,则返回 (off_t) -1。这可能是由于缺少对文件定位或内部错误的支持而发生的。否则返回值为当前文件位置。

该函数是 Unix 单一规范版本 2 中定义的扩展。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 ftello64。即,LFS 接口透明地替换旧接口。

函数:off64_t ftello64 (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

这个函数与 ftello 类似,唯一的区别是返回值是 off64_t 类型。这还要求使用 fopen64、freopen64 或 tmpfile64 打开流流,否则将文件指针定位到 2^31 字节限制之外的基础文件操作可能会失败。

如果源代码是在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译的,则此函数在名称 ftello 下可用,因此可以透明地替换旧接口。

函数:int fseek (FILE *stream, long int offset, int wherece)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

fseek 函数用于改变流流的文件位置。wherece 的值必须是常量 SEEK_SET、SEEK_CUR 或 SEEK_END 之一,以分别指示偏移量是相对于文件开头、当前文件位置还是文件结尾。

如果操作成功,此函数返回零值,如果操作失败,则返回非零值。成功的调用还会清除流的文件结束指示符,并丢弃任何因使用 ungetc 被“推回”的字符。

fseek 要么在设置文件位置之前刷新任何缓冲的输出,要么记住它,以便稍后将其写入文件中的适当位置。

函数:int fseeko (FILE *stream, off_t offset, int wherece)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数类似于 fseek,但它更正了 fseek 在具有 POSIX 类型的系统中的问题。使用 long int 类型的值作为偏移量与 POSIX 不兼容。fseeko 使用正确的类型 off_t 作为 offset 参数。

出于这个原因,最好在 ftello 可用时首选它,因为它的功能(如果完全不同)更接近底层定义。

功能和返回值与 fseek 相同。

该函数是 Unix 单一规范版本 2 中定义的扩展。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 fseeko64。即,LFS 接口透明地替换旧接口。

函数:int fseeko64 (FILE *stream, off64_t offset, int wherece)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

这个函数与 fseeko 类似,唯一的区别是 offset 参数是 off64_t 类型。这还要求使用 fopen64、freopen64 或 tmpfile64 打开流流,否则将文件指针定位到 2^31 字节限制之外的基础文件操作可能会失败。

如果源代码是在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译的,则此函数在名称 fseeko 下可用,因此可以透明地替换旧接口。

可移植性注意:在非 POSIX 系统中,ftell、ftello、fseek 和 fseeko 可能仅在二进制流上可靠地工作。请参阅文本和二进制流。

以下符号常量被定义为用作 fseek 的 whence 参数。它们还与 lseek 函数一起使用(请参阅输入和输出原语)并指定文件锁的偏移量(请参阅文件的控制操作)。

宏:int SEEK_SET

这是一个整数常量,当用作 fseek 或 fseeko 函数的 whence 参数时,指定提供的偏移量是相对于文件开头的。

宏:int SEEK_CUR

这是一个整数常量,当用作 fseek 或 fseeko 函数的 whence 参数时,指定提供的偏移量是相对于当前文件位置的。

宏:int SEEK_END

这是一个整数常量,当用作 fseek 或 fseeko 函数的 whence 参数时,指定提供的偏移量是相对于文件末尾的。

函数:void rewind (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

rewind 函数将流放在文件的开头。它等效于在偏移量参数为 0L 且 whence 参数为 SEEK_SET 的流上调用 fseek 或 fseeko,不同之处在于丢弃返回值并重置流的错误指示符。

“SEEK_…”常量的这三个别名的存在是为了与旧 BSD 系统兼容。它们在两个不同的头文件中定义:fcntl.h 和 sys/file.h。

L_SET

SEEK_SET 的别名。

L_INCR

SEEK_CUR 的别名。

L_XTND

SEEK_END 的别名。

2.19. 便携式文件定位函数

Portable File-Position Functions

在 GNU 系统上,文件位置是真正的字符数。您可以将任何字符计数值指定为 fseek 或 fseeko 的参数,并为任何随机访问文件获得可靠的结果。但是,某些 ISO C 系统不以这种方式表示文件位置。

在文本流与二进制流真正不同的某些系统上,不可能将文本流的文件位置表示为从文件开头开始的字符数。例如,某些系统上的文件位置必须同时编码文件内的记录偏移量和记录内的字符偏移量。

因此,如果您希望您的程序可移植到这些系统,您必须遵守某些规则:

  • 从文本流上的 ftell 返回的值与您目前已阅读的字符数没有可预测的关系。您唯一可以依赖的是,您可以随后将其用作 fseek 或 fseeko 的偏移参数以移回相同的文件位置。
  • 在对文本流调用 fseek 或 fseeko 时,偏移量必须为零,或者 wherece 必须是 SEEK_SET,并且偏移量必须是先前在同一流上调用 ftell 的结果。
  • 文本流的文件位置指示符的值是未定义的,而有些字符已被 ungetc 推回,但尚未被读取或丢弃。请参阅未读。

但是即使你遵守了这些规则,对于长文件,你可能仍然会遇到麻烦,因为 ftell 和 fseek 使用 long int 值来表示文件位置。这种类型可能没有空间对大文件中的所有文件位置进行编码。在这里使用 ftello 和 fseeko 函数可能会有所帮助,因为 off_t 类型预计能够保存所有文件位置值,但这仍然无助于处理必须与文件位置相关联的附加信息。

因此,如果您确实想支持对文件位置具有特殊编码的系统,最好使用 fgetpos 和 fsetpos 函数。这些函数使用数据类型 fpos_t 表示文件位置,其内部表示因系统而异。

这些符号在头文件 stdio.h 中声明。

数据类型:fpos_t

这是可以对有关流的文件位置的信息进行编码的对象类型,供函数 fgetpos 和 fsetpos 使用。

在 GNU C 库中,fpos_t 是一个不透明的数据结构,其中包含表示文件偏移和转换状态信息的内部数据。在其他系统中,它可能具有不同的内部表示。

在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译时,这种类型实际上等同于 fpos64_t,因为 LFS 接口透明地替换了旧接口。

数据类型:fpos64_t

这是可以对有关流的文件位置的信息进行编码的对象类型,供函数 fgetpos64 和 fsetpos64 使用。

在 GNU C 库中,fpos64_t 是一个不透明的数据结构,其中包含表示文件偏移和转换状态信息的内部数据。在其他系统中,它可能具有不同的内部表示。

函数:int fgetpos (FILE *stream, fpos_t *position)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数将流流的文件位置指示符的值存储在位置指向的 fpos_t 对象中。如果成功,fgetpos 返回零;否则它返回一个非零值并将实现定义的正值存储在 errno 中。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,该函数实际上是 fgetpos64。即,LFS 接口透明地替换旧接口。

函数:int fgetpos64 (FILE *stream, fpos64_t *position)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数与 fgetpos 类似,但文件位置以 fpos64_t 类型的变量返回,该变量指向该位置。

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码,则此函数在名称 fgetpos 下可用,因此可以透明地替换旧接口。

函数:int fsetpos (FILE *stream, const fpos_t *position)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数将流流的文件位置指示器设置为位置位置,该位置必须由先前在同一流上调用 fgetpos 设置。如果成功,fsetpos 清除流上的文件结束指示符,丢弃任何被使用 ungetc “推回”的字符,并返回零值。否则,fsetpos 返回一个非零值并将实现定义的正值存储在 errno 中。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,该函数实际上是 fsetpos64。即,LFS 接口透明地替换旧接口。

函数:int fsetpos64 (FILE *stream, const fpos64_t *position)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数类似于 fsetpos,但用于定位的文件位置在 fpos64_t 类型的变量中提供,该位置指向该变量。

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码,则此函数在名称 fsetpos 下可用,因此可以透明地替换旧接口。

2.20. 流缓冲

Stream Buffering

写入流的字符通常会累积并异步传输到块中的文件,而不是在应用程序输出后立即出现。类似地,流通常以块的形式而不是逐个字符地从主机环境中检索输入。这称为缓冲。

如果您正在编写使用流进行交互式输入和输出的程序,则在设计程序的用户界面时需要了解缓冲的工作原理。否则,您可能会发现输出(例如进度或提示消息)未按您的预期显示,或者显示其他一些意外行为。

本节仅涉及控制何时在流和文件或设备之间传输字符,而不涉及如何在特定类别的设备上处理回显、流控制等。有关终端设备上常见控制操作的信息,请参阅底层终端接口。

您可以通过使用对文件描述符进行操作的低级输入和输出函数来完全绕过流缓冲设施。请参阅底层输入/输出。

2.20.1. 缓冲概念

Buffering Concepts

有三种不同的缓冲策略:

  • 写入或读取无缓冲流的字符会尽快单独传输到文件或从文件中传输。
  • 当遇到换行符时,写入行缓冲流的字符会以块的形式传输到文件中。
  • 写入或读取完全缓冲流的字符以任意大小的块传输到文件或从文件传输。

新打开的流通常是完全缓冲的,但有一个例外:连接到交互式设备(例如终端)的流最初是行缓冲的。有关如何选择不同类型的缓冲的信息,请参阅控制哪种缓冲。通常,自动选择会为您打开的文件或设备提供最方便的缓冲。

交互式设备使用行缓冲意味着以换行符结尾的输出消息将立即出现——这通常是您想要的。不以换行符结尾的输出可能会或可能不会立即显示,因此如果您希望它们立即显示,您应该使用 fflush 显式刷新缓冲输出,如刷新缓冲区中所述。

2.20.2. 冲洗缓冲液

在缓冲流上刷新输出意味着将所有累积的字符传输到文件。在很多情况下,流上的缓冲输出会自动刷新:

  • 当您尝试进行输出并且输出缓冲区已满时。
  • 当流关闭时。请参阅关闭流。
  • 当程序通过调用 exit 终止时。请参阅正常终止。
  • 写入换行符时,如果流是行缓冲的。
  • 每当对任何流的输入操作实际从其文件中读取数据时。

如果您想在其他时间刷新缓冲的输出,请调用 fflush,它在头文件 stdio.h 中声明。

函数:int fflush (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数将流上的任何缓冲输出传递到文件。如果 stream 是空指针,则 fflush 会刷新所有打开的输出流上的缓冲输出。

如果发生写入错误,此函数返回 EOF,否则返回零。

函数:int fflush_unlocked (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

fflush_unlocked 函数与 fflush 函数等效,只是它不隐式锁定流。

fflush 函数可用于刷新当前打开的所有流。虽然这在某些情况下很有用,但它通常比必要的更多,因为它可能在需要终端输入并且程序希望确保所有输出在终端上可见的情况下完成。但这意味着只有行缓冲的流必须被刷新。Solaris 专门为此引入了一个功能。它总是以某种形式在 GNU C 库中可用,但从未正式导出。

函数:void _flushlbf (void)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

_flushlbf 函数刷新当前打开的所有行缓冲流。

该函数在 stdio_ext.h 头文件中声明。

兼容性说明:众所周知,一些脑部受损的操作系统非常专注于面向行的输入和输出,以至于刷新行缓冲流会导致写入换行符!幸运的是,这个“功能”似乎变得不那么普遍了。使用 GNU C 库,您无需担心这一点。

在某些情况下,不刷新流的待处理输出而是简单地忘记它可能很有用。如果传输成本高昂并且不再需要输出,这是有效的推理。在这种情况下,可以使用 Solaris 中引入并在 GNU C 库中可用的非标准函数。

函数:void __fpurge (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Unsafe corrupt | See POSIX Safety Concepts.

__fpurge 函数导致流流的缓冲区被清空。如果流当前处于读取模式,则缓冲区中的所有输入都将丢失。如果流处于输出模式,则缓冲输出不会写入设备(或任何其他底层存储)并且缓冲区被清除。

此函数在 stdio_ext.h 中声明。

2.20.3. 控制哪种缓冲

Controlling Which Kind of Buffering

打开流之后(但在对其执行任何其他操作之前),您可以使用 setvbuf 函数明确指定您希望它具有哪种缓冲。

本节中列出的工具在头文件 stdio.h 中声明。

函数:int setvbuf (FILE *stream, char *buf, int mode, size_t size)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

该函数用于指定流流应该具有缓冲模式模式,可以是_IOFBF(用于全缓冲)、_IOLBF(用于行缓冲)或_IONBF(用于无缓冲输入/输出)。

如果您指定一个空指针作为 buf 参数,那么 setvbuf 会使用 malloc 自己分配一个缓冲区。当您关闭流时,此缓冲区将被释放。

否则,buf 应该是一个至少可以容纳 size 个字符的字符数组。只要流保持打开状态并且该数组保持其缓冲区,您就不应为该数组释放空间。您通常应该静态分配它,或者 malloc(请参阅无约束分配)缓冲区。除非在退出声明数组的块之前关闭文件,否则使用自动数组不是一个好主意。

虽然数组仍然是一个流缓冲区,但流 I/O 函数将使用该缓冲区来实现其内部目的。当流使用它进行缓冲时,您不应该尝试直接访问数组中的值。

setvbuf 函数在成功时返回零,如果 mode 的值无效或请求无法得到满足,则返回非零值。

宏:int _IOFBF

此宏的值是一个整数常量表达式,可用作 setvbuf 函数的模式参数,以指定流应完全缓冲。

宏:int _IOLBF

此宏的值是一个整数常量表达式,可用作 setvbuf 函数的模式参数,以指定流应为行缓冲。

宏:int _IONBF

此宏的值是一个整数常量表达式,可用作 setvbuf 函数的模式参数,以指定流应该是无缓冲的。

宏:int BUFSIZ

该宏的值是一个整数常量表达式,可以很好地用于 setvbuf 的 size 参数。此值保证至少为 256。

在每个系统上选择 BUFSIZ 的值以使流 I/O 高效。因此,在调用 setvbuf 时,最好使用 BUFSIZ 作为缓冲区的大小。

实际上,您可以通过 fstat 系统调用获得更好的缓冲区大小值:它位于文件属性的 st_blksize 字段中。请参阅文件属性的含义。

有时人们还使用 BUFSIZ 作为用于相关目的的缓冲区的分配大小,例如用于接收一行输入的字符串 fgets(参见字符输入)。没有特别的理由为此使用 BUFSIZ 而不是任何其他整数,除非它可能导致以有效大小的块执行 I/O。

函数:void setbuf (FILE *stream, char *buf)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

如果 buf 为空指针,则此函数的效果等同于使用 _IONBF 模式参数调用 setvbuf。否则,等效于调用 setvbuf 并带有 buf,模式为 _IOFBF,大小参数为 BUFSIZ。

提供 setbuf 函数是为了兼容旧代码;在所有新程序中使用 setvbuf。

函数:void setbuffer (FILE *stream, char *buf, size_t size)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

如果 buf 是空指针,则此函数使流无缓冲。否则,它将使用 buf 作为缓冲区使流完全缓冲。size 参数指定 buf 的长度。

提供此功能是为了与旧 BSD 代码兼容。请改用 setvbuf。

函数:void setlinebuf (FILE *stream)

Preliminary: | MT-Safe | AS-Unsafe corrupt | AC-Unsafe lock corrupt | See POSIX Safety Concepts.

此函数使流成为行缓冲,并为您分配缓冲区。

提供此功能是为了与旧 BSD 代码兼容。请改用 setvbuf。

可以使用 Solaris 中引入并在 GNU C 库中可用的非标准函数来查询给定流是否是行缓冲的。

函数:int __flbf (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果流流是行缓冲的,__flbf 函数将返回一个非零值。否则返回值为零。

该函数在 stdio_ext.h 头文件中声明。

另外两个扩展允许确定缓冲区的大小以及使用了多少。Solaris 中也引入了这些函数。

函数:size_t __fbufsize (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Safe | See POSIX Safety Concepts.

__fbufsize 函数返回流流中缓冲区的大小。该值可用于优化流的使用。

该函数在 stdio_ext.h 头文件中声明。

函数:size_t __fpending (FILE *stream)

Preliminary: | MT-Safe race:stream | AS-Unsafe corrupt | AC-Safe | See POSIX Safety Concepts.

__fpending 函数返回当前在输出缓冲区中的字节数。对于面向宽的流,测量单位是宽字符。此函数不应用于处于读取模式或以只读方式打开的缓冲区。

该函数在 stdio_ext.h 头文件中声明。

2.21. 其他类型的流

Other Kinds of Streams

GNU C 库为您提供了定义其他类型的流的方法,这些流不一定对应于打开的文件。

一种这样的流类型从字符串获取输入或将输出写入字符串。这些类型的流在内部用于实现 sprintf 和 sscanf 函数。您还可以使用字符串流中描述的函数显式创建这样的流。

更一般地,您可以使用程序提供的函数定义对任意对象进行输入/输出的流。该协议在 Programming Your Own Custom Streams 中讨论。

可移植性说明:本节中描述的工具是特定于 GNU 的。其他系统或 C 实现可能会也可能不会提供等效的功能。

2.21.1. 字符串流

String Streams

fmemopen 和 open_memstream 函数允许您对字符串或内存缓冲区进行 I/O。这些设施在 stdio.h 中声明。

函数:FILE * fmemopen (void *buf, size_t size, const char *opentype)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem lock | See POSIX Safety Concepts.

此函数打开一个流,该流允许 opentype 参数指定的访问,该流从参数 buf 指定的缓冲区读取或写入。该数组的长度必须至少为 size 个字节。

如果您指定一个空指针作为 buf 参数,fmemopen 会动态分配一个大小为字节长的数组(与 malloc 一样;请参阅无约束分配)。这仅在您要将内容写入缓冲区然后再次读回它们时才真正有用,因为您无法实际获取指向缓​​冲区的指针(为此,请尝试下面的 open_memstream)。当流关闭时,缓冲区被释放。

参数 opentype 与 fopen 中的相同(请参阅打开流)。如果 opentype 指定追加模式,则初始文件位置设置为缓冲区中的第一个空字符。否则初始文件位置在缓冲区的开头。

当一个打开写入的流被刷新或关闭时,如果合适的话,一个空字符(零字节)会被写入缓冲区的末尾。您应该在 size 参数中添加一个额外的字节来解决这个问题。尝试将超过 size 字节写入缓冲区会导致错误。

对于打开以供读取的流,缓冲区中的空字符(零字节)不算作“文件结尾”。仅当文件位置超过 size 字节时,读取操作才指示文件结束。因此,如果要从以空字符结尾的字符串中读取字符,则应提供字符串的长度作为 size 参数。

下面是一个使用 fmemopen 创建用于读取字符串的流的示例:

#include <stdio.h>static char buffer[] = "foobar";int
main (void)
{int ch;FILE *stream;stream = fmemopen (buffer, strlen (buffer), "r");while ((ch = fgetc (stream)) != EOF)printf ("Got %c\n", ch);fclose (stream);return 0;
}

该程序产生以下输出:

Got f
Got o
Got o
Got b
Got a
Got r

函数:FILE * open_memstream (char **ptr, size_t *sizeloc)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

此函数打开用于写入缓冲区的流。缓冲区是动态分配的,并根据需要使用 malloc 增长。关闭流后,您需要负责使用 free 或 realloc 清理此缓冲区。请参阅无约束分配。

当流用 fclose 关闭或用 fflush 刷新时,位置 ptr 和 sizeloc 被更新以包含指向缓冲区的指针及其大小。如此存储的值仅在流上没有进一步输出时才保持有效。如果您进行更多输出,则必须再次刷新流以存储新值,然后才能再次使用它们。

在缓冲区的末尾写入一个空字符。此空字符不包含在 sizeloc 中存储的大小值中。

您可以使用 fseek 或 fseeko 移动流的文件位置(请参阅文件定位)。将文件位置移到已写入数据的末尾会用零填充中间空间。

下面是一个使用 open_memstream 的例子:

#include <stdio.h>int
main (void)
{char *bp;size_t size;FILE *stream;stream = open_memstream (&bp, &size);fprintf (stream, "hello");fflush (stream);printf ("buf = `%s', size = %zu\n", bp, size);fprintf (stream, ", world");fclose (stream);printf ("buf = `%s', size = %zu\n", bp, size);return 0;
}

该程序产生以下输出:

buf = `hello', size = 5
buf = `hello, world', size = 12

2.21.2. 编写自己的自定义流

Programming Your Own Custom Streams

本节介绍如何创建从任意数据源获取输入或将输出写入您编程的任意数据接收器的流。我们称这些自定义流。这里描述的函数和类型都是 GNU 扩展。

2.21.2.1. 自定义流和 Cookie

Custom Streams and Cookies

每个自定义流内部都有一个称为 cookie 的特殊对象。这是您提供的一个对象,它记录在何处获取或存储读取或写入的数据。您可以定义用于 cookie 的数据类型。库中的流函数从不直接引用其内容,甚至不知道类型是什么;他们用 void * 类型记录它的地址。

要实现自定义流,您必须指定如何在指定位置获取或存储数据。您可以通过定义挂钩函数来读取、写入、更改“文件位置”和关闭流来做到这一点。所有这四个函数都将被传递到流的 cookie 中,这样它们就可以知道在哪里获取或存储数据。库函数不知道 cookie 中的内容,但您的函数会知道。

创建自定义流时,必须指定 cookie 指针,以及存储在 cookie_io_functions_t 类型结构中的四个挂钩函数。

这些设施在 stdio.h 中声明。

数据类型:cookie_io_functions_t

这是一个结构类型,它包含定义流和它的 cookie 之间的通信协议的函数。它有以下成员:

cookie_read_function_t *read

这是从 cookie 中读取数据的函数。如果值是空指针而不是函数,则对该流的读取操作始终返回 EOF。

cookie_write_function_t *write

这是将数据写入 cookie 的函数。如果值是空指针而不是函数,则写入流的数据将被丢弃。

cookie_seek_function_t *seek

这是在 cookie 上执行等效文件定位的函数。如果值是空指针而不是函数,则在此流上调用 fseek 或 fseeko 只能查找缓冲区内的位置;任何在缓冲区外寻找的尝试都将返回 ESPIPE 错误。

cookie_close_function_t *close

此函数在关闭流时对 cookie 执行任何适当的清理。如果该值是空指针而不是函数,则在关闭流时关闭 cookie 没有什么特别的。

函数:FILE * fopencookie (void *cookie, const char *opentype, cookie_io_functions_t io-functions)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem lock | See POSIX Safety Concepts.

该函数实际上使用 io-functions 参数中的函数创建用于与 cookie 通信的流。opentype 参数被解释为 fopen;请参阅打开流。(但请注意,“打开时截断”选项被忽略。)新流被完全缓冲。

fopencookie 函数返回新创建的流,或在发生错误时返回空指针。

2.21.2.2. 自定义流挂钩函数

Custom Stream Hook Functions

以下是有关如何定义自定义流所需的四个挂钩函数的更多详细信息。

您应该将函数定义为从 cookie 中读取数据:

ssize_t reader (void *cookie, char *buffer, size_t size)

这与读取功能非常相似;请参阅输入和输出原语。您的函数应将最多 size 个字节传输到缓冲区中,并返回读取的字节数,或返回零以指示文件结束。您可以返回值 -1 以指示错误。

您应该将向 cookie 写入数据的函数定义为:

ssize_t writer (void *cookie, const char *buffer, size_t size)

这与 write 函数非常相似;请参阅输入和输出原语。您的函数应该从缓冲区传输最多 size 个字节,并返回写入的字节数。您可以返回值 0 来指示错误。您不得返回任何负值。

您应该将函数定义为对 cookie 执行搜索操作:

int seeker (void *cookie, off64_t *position, int wherece)

对于这个函数,position 和 wherece 参数被解释为 fgetpos;请参阅便携式文件定位功能。

执行查找操作后,您的函数应将生成的文件位置存储在相对于文件开头的位置。您的函数应在成功时返回值 0,并返回 -1 以指示错误。

您应该将函数定义为对适合关闭流的 cookie 执行清理操作:

int cleaner (void *cookie)

您的函数应返回 -1 表示错误,否则返回 0。

数据类型:cookie_read_function_t

这是自定义流的读取函数应具有的数据类型。如果您如上所示声明函数,这就是它将具有的类型。

数据类型:cookie_write_function_t

自定义流的写入函数的数据类型。

数据类型:cookie_seek_function_t

自定义流的 seek 函数的数据类型。

数据类型:cookie_close_function_t

自定义流的关闭函数的数据类型。

2.22. 格式化消息

Formatted Messages

在基于 System V 的系统上,程序(尤其是系统工具)的消息使用 fmtmsg 函数以严格的形式打印。一致性有时有助于用户解释消息,fmtmsg 函数的严格测试确保程序员遵循一些最低要求。

2.22.1. 打印格式化消息

Printing Formatted Messages

消息可以打印到标准错误和/或控制台。要选择目标,程序员可以使用以下两个值,如果需要,可以按位或组合,作为 fmtmsg 的分类参数:

MM_PRINT

以标准错误显示消息。

MM_CONSOLE

在系统控制台上显示消息。

系统的错误部分可以通过以下值之一发出信号,该值也与 fmtmsg 的分类参数进行按位或运算:

MM_HARD

条件的来源是一些硬件。

MM_SOFT

条件的来源是一些软件。

MM_FIRM

条件的来源是一些固件。

fmtmsg 的分类参数的第三个组成部分可以描述检测问题的系统部分。这是通过使用以下值之一来完成的:

MM_APPL

应用程序检测到错误条件。

MM_UTIL

错误情况由实用程序检测到。

MM_OPSYS

操作系统检测到错误情况。

分类的最后一个组成部分可以表示此消息的结果。可以使用以下值之一:

MM_RECOVER

这是一个可恢复的错误。

MM_NRECOV

这是一个不可恢复的错误。

函数:int fmtmsg (long int classification, const char *label, int severity, const char *text, const char *action, const char *tag)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Safe | See POSIX Safety Concepts.

在分类参数中指定的设备上显示由其参数描述的消息。label 参数标识消息的来源。该字符串应由两个冒号分隔的部分组成,其中第一部分不超过 10 个字符,第二部分不超过 14 个字符。text 参数描述了错误的条件,action 参数描述了从错误中恢复的可能步骤,tag 参数是对在线文档的参考,可以在其中找到更多信息。它应包含标签值和唯一标识号。

每个参数都可以是一个特殊值,这意味着该值将被省略。这些值的符号名称是:

MM_NULLLBL

忽略标签参数。

MM_NULLSEV

忽略严重性参数。

MM_NULLMC

忽略分类参数。这意味着实际上没有打印任何内容。

MM_NULLTXT

忽略文本参数。

MM_NULLACT

忽略动作参数。

MM_NULLTAG

忽略标签参数。

还有另一种方法可以将某些字段从输出中省略为标准错误。这在下面对影响行为的环境变量的描述中进行了描述。

严重性参数可以具有下表中的值之一:

MM_NOSEV

不打印任何内容,此值与 MM_NULLSEV 相同。

MM_HALT

该值打印为 HALT。

MM_ERROR

该值打印为 ERROR。

MM_WARNING

此值打印为 WARNING。

MM_INFO

此值打印为 INFO。

这五个宏的数值介于 0 和 4 之间。使用环境变量 SEV_LEVEL 或使用addseverity 函数可以添加更多严重级别,并使用相应的字符串进行打印。这在下面进行了描述(请参阅添加严重性类别)。

如果没有忽略任何参数,则输出如下所示:

label: severity-string: text
TO FIX: action tag

如有必要,即如果相应的参数未被忽略,则插入冒号、换行符和 TO FIX 字符串。

X/Open 可移植性指南中指定了此功能。它也适用于从 System V 派生的所有系统。

如果没有发生错误,该函数将返回值 MM_OK。如果仅打印到标准错误失败,则返回 MM_NOMSG。如果打印到控制台失败,则返回 MM_NOCON。如果没有打印 MM_NOTOK 则返回。在所有输出失败的情况下,如果参数值不正确,也会返回最后一个值。

有两个环境变量会影响 fmtmsg 的行为。第一个是 MSGVERB。它用于控制实际发生在标准错误上的输出(而不是控制台输出)。这五个字段中的每一个都可以显式启用。为此,用户必须在第一次调用 fmtmsg 函数之前将格式如下的 MSGVERB 变量放入环境中:

MSGVERB=keyword[:keyword[:…]]

有效的关键字是标签、严重性、文本、操作和标签。如果环境变量没有给出或者是空字符串,给出了一个不支持的关键字或者该值在某种程度上是无效的,那么消息的任何部分都不会被屏蔽掉。

第二个影响 fmtmsg 行为的环境变量是 SEV_LEVEL。X/Open 可移植性指南中未指定此变量和 fmtmsg 行为的变化。不过,它在 System V 系统中可用。它可用于引入新的严重性级别。默认情况下,只有上述五个严重级别可用。任何其他数值都会使 fmtmsg 不打印任何内容。

如果用户将 SEV_LEVEL 的格式设置为

SEV_LEVEL=[description[:description[:…]]]

在第一次调用 fmtmsg 之前的进程环境中,其中 description 具有表单的值

severity-keyword,level,printstring

fmtmsg 不使用严重性关键字部分,但它必须存在。级别部分是数字的字符串表示形式。数值必须是大于 4 的数字。必须在 fmtmsg 的严重性参数中使用该值才能选择此类。无法覆盖任何预定义类。printstring 是 fmtmsg 处理此类消息时打印的字符串(见上文,fmtsmg 不打印数值,而是打印字符串表示形式)。

2.22.2. 添加严重等级

Adding Severity Classes

除了使用环境变量 SEV_LEVEL 之外,还有另一种引入严重性等级的可能性。这简化了在正在运行的程序中引入新类的任务。可以使用 setenv 或 putenv 函数来设置环境变量,但这很麻烦。

函数:int addeverity (int severity, const char *string)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe lock mem | See POSIX Safety Concepts.

该函数允许引入新的严重性等级,可以通过 fmtmsg 函数的严重性参数来处理这些等级。addeverity 的severity 参数必须与fmtmsg 同名参数的值匹配,string 是实际消息中打印的字符串,而不是数值。

如果 string 为 NULL,则删除具有根据严重性的数值的严重性类。

无法覆盖或删除默认严重性类别之一。所有将严重性设置为默认类值之一的 addeverity 调用都将失败。

如果任务成功执行,则返回值为 MM_OK。如果返回值为 MM_NOTOK 则出现问题。这可能意味着没有更多内存可用,或者当必须删除某个类时它不可用。

尽管 fmtsmg 函数在 X/Open 可移植性指南中指定了该函数。它在 System V 系统上可用。

2.22.3. 如何使用 fmtmsg 和 addeverity

How to use fmtmsg and addseverity

这是一个简单的示例程序,用于说明本节中描述的两个函数的使用。

#include <fmtmsg.h>int
main (void)
{addseverity (5, "NOTE2");fmtmsg (MM_PRINT, "only1field", MM_INFO, "text2", "action2", "tag2");fmtmsg (MM_PRINT, "UX:cat", 5, "invalid syntax", "refer to manual","UX:cat:001");fmtmsg (MM_PRINT, "label:foo", 6, "text", "action", "tag");return 0;
}

第二次调用 fmtmsg 说明了这个函数的使用,因为它通常发生在 System V 系统上,系统 V 系统大量使用这个函数。似乎值得在这里简要解释一下这个系统如何在 System V 上工作。标签字段 (UX:cat) 的值表明错误发生在 Unix 程序 cat 中。错误的解释如下,action 参数的值是“参考手册”。如有必要,可以在此处更具体。如上所述,标签字段包含为标签参数指定的字符串的值,以及一个唯一的 ID(在本例中为 001)。对于 GNU 环境,此字符串可以包含对程序信息页面中相应节点的引用。

在不指定 MSGVERB 和 SEV_LEVEL 函数的情况下运行此程序会产生以下输出:

UX:cat: NOTE2: invalid syntax
TO FIX: refer to manual UX:cat:001

我们看到消息的不同字段以及额外的胶水(冒号和 TO FIX 字符串)是如何打印的。但是对 fmtmsg 的三个调用中只有一个产生了输出。第一次调用不打印任何内容,因为标签参数的格式不正确。该字符串必须包含两个字段,用冒号分隔(请参阅打印格式化消息)。第三个 fmtmsg 调用没有产生任何输出,因为没有定义具有数值 6 的类。尽管默认情况下也没有定义数值为 5 的类,但调用 addeverity 会引入它,第二次调用 fmtmsg 会产生上述输出。

当我们将程序的环境更改为包含 SEV_LEVEL=XXX,6,NOTE 运行它时,我们会得到不同的结果:

UX:cat: NOTE2: invalid syntax
TO FIX: refer to manual UX:cat:001
label:foo: NOTE: text
TO FIX: action tag

现在对 fmtmsg 的第三次调用产生了一些输出,我们可以看到来自环境变量的字符串 NOTE 是如何出现在消息中的。

现在我们可以通过指定我们感兴趣的字段来减少输出。如果我们另外将环境变量 MSGVERB 设置为值 severity

glibc 知:手册12:输入/输出流相关推荐

  1. 【学习笔记+习题集】字符相关(输入输出流,字典树,AC自动机,后缀自动机)(4598字)(更新至2022.12.28)

    目录 板块零:输入输出流 情况一:读取字符串和读取行混用的时候 情况二:关于识别空行 第一题:hdoj2072 情况三:用char数组接受getline函数的输入流 情况四:关于汉字 补充练习: 第一 ...

  2. glibc 知:手册14:文件系统接口

    文章目录 1. 前言 2. 文件系统接口 2.1. 工作目录 2.2. 访问目录 2.2.1. 目录条目的格式 2.2.2. 打开目录流 2.2.3. 读取和关闭目录流 2.2.4. 列出目录的简单程 ...

  3. php 输入 输出,php的文件输入输出流php://input

    原标题: php的文件输入输出流php://input PHP输入流php://input 在使用xml-rpc的时候,server端获取client数据,主要是通过php输入流input,而不是$_ ...

  4. C++学习笔记-第7单元-文件输入输出流

    C++学习笔记-第7单元 文章目录 C++学习笔记-第7单元 第7单元 文件输入输出流 单元导读 7.1 [C++17]文件系统 7.1.1 C++17的文件系统库简介 7.1.2 路径类及操作 7. ...

  5. linux初学基本命令,Linux初学者一定要知道的12个基本命令

    玩蛇网推荐图文教程:python 列表 Linux初学者一定要知道的12个基本命令.Linux新用户首先要知道的就是命令,什么时命令,最基本的命令有哪几个?让我们来看看Linux用户应该知道的一些基本 ...

  6. 【java开发系列】—— java输入输出流

    前言 任何语言输入输出流都是很重要的部分,比如从一个文件读入内容,进行分析,或者输出到另一个文件等等,都需要文件流的操作.这里简单介绍下reader,wirter,inputstream,output ...

  7. Java输入输出流IO

    1.什么是IO Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列.Java的I/O流提供了读 ...

  8. 1.7 Java字符流的使用:字符输入/输出流、字符文件和字符缓冲区的输入/输出流

    尽管 Java 中字节流的功能十分强大,几乎可以直接或间接地处理任何类型的输入/输出操作,但利用它却不能直接操作 16 位的 Unicode 字符.这就要用到字符流.本节将重点介绍字符流的操作. 字符 ...

  9. java 对象读写_java 对象输入输出流读写文件的操作实例

    java 对象输入输出流读写文件的操作实例 java 支持对对象的读写操作,所操作的对象必须实现Serializable接口. 实例代码: package vo; import java.io.Ser ...

最新文章

  1. Log4j日志管理的用法
  2. 指针学习笔记(更新中)
  3. 什么是系统调用?为什么要用系统调用?
  4. ITK:提取二值图像中连接区域的边界
  5. 使用VS Code开发asp.net core (上)
  6. input type=file图片上传时,先预览
  7. 华为交换机 查ip冲突_华为交换机:如何解决网络中IP地址发生冲突故障?
  8. localStorage的跨与实现方案
  9. java hdfs ha_hadoop2.x hdfs完全分布式 HA 搭建
  10. Opencv之高效函数convertTo
  11. Python常用基础语法
  12. 初步熟悉RHEL 8
  13. 百度、360、搜狗、神马的SEO搜索结果php爬取排名。
  14. python 战棋游戏代码实现(1):生物行走和攻击选择
  15. 字体侵权太严重,我准备了700款可商用字体
  16. 新浪新闻发布Z世代洞察报告:Z世代偏爱深入“吃瓜” 对元宇宙兴趣强烈
  17. 四大CPU架构的区别
  18. Java-opts变量没生效,使用JAVA_OPTS env变量运行java无效
  19. 百鸡问题扩展-N鸡问题
  20. Chat-REC、InstructRec(LLM大模型用于推荐系统)

热门文章

  1. mac linux 性能测试工具下载,8款SSD固态硬盘性能测试软件,适用于Windows、Linux、MacOS、安卓系统等不同操作系统的...
  2. 基于Jenkins的DevOps流水线实践教程|2020全新制作|端到端研发效能提升
  3. Java计算出生一万天的纪念日期(错误演示,未解决)-----已经解决(原因计算long型数字未加L)
  4. autocad是计算机软件吗,AutoCAD软件属于计算机辅助设计软件()
  5. 从赛灵思Kintex-7认识FPGA
  6. 中国古代数学在几何学领域的独特贡献
  7. 测试用例设计等级划分
  8. golang小案例 —— 剪子剪子包袱锤小游戏
  9. 如何彻底卸载系统自带的IE浏览器
  10. 可擦写光盘不能擦除和刻写_光盘的分类,光盘的擦除与刻录。