【问题标题】:Does a byte oriented FILE stream contain `char`s or `unsigned char`s?面向字节的 FILE 流是否包含 `char` 或 `unsigned char`?
【发布时间】:2012-09-23 17:49:19
【问题描述】:

我已通读 C11 标准的第 7.21 节,其中描述了 <stdio.h>。该标准首先将流描述为:

7.21.2.2:

文本流是一个有序的字符序列...

7.21.2.3:

二进制流是一个有序的字符序列...

没有指定流字符的类型(因为这取决于方向)。后来说:

7.21.3.12:

...字节输出函数将字符写入流,就像通过连续调用 fputc 函数一样。

来自fputc (7.21.7.3.2):

fputc函数将c指定的字符(转换为unsigned char)写入stream指向的输出流...

这表示fputcint c 参数在写入流之前转换为unsigned charfgetc 也有类似的注释:

7.21.7.1.2:

fgetc 函数将该字符作为unsigned char 转换为int

ungetcfreadfwrite

现在这一切都暗示在内部,面向字节的流由unsigned chars 表示。

但是,查看 Linux 内核的内部结构,文件似乎被视为char 的流。我这么说的一个原因是 file_operations readwrite 回调分别得到 char __user *const char __user *

glibc的实现中,FILEstruct _IO_FILEtypedef,在libio/libio.h中定义。同样在这个struct中,所有的读写指针都是char *

在 C++ 中,basic_ostream::write 函数以 const char * 作为输入,同样以 basic_istream::read 为输入(但我对这个问题中的 C++ 不感兴趣)。

我的问题是,上面的引用是否暗示 FILE 流应该作为unsigned char 的流受到威胁?如果是这样,为什么glibc 和Linux 内核用char * 来实现它们?如果不是,为什么标准坚持将字符转换为unsigned char

【问题讨论】:

  • fgetc 的可能返回之一是错误代码(形式为EOF)。为了将错误代码与任何有效字符区分开来,标准将字符转换为unsigned char,然后转换为int。所以绝对没有办法将EOF 解释为有效字符或任何有效字符作为错误代码。
  • @pmg,这是一个很好的观点。对于fgetc,这是有道理的,因为例如-1 作为字符将返回为255。对于fputcungetc,它们从int 转换为unsigned char,但是我看不到任何好处。我的意思是,如果您将它们存储为char,那么char c = (unsigned char)ichar c = iiint 输入)是相等的,不是吗?
  • Simmetry 是追求的好特性:)
  • @pmg,我完全同意那个

标签: c stdio


【解决方案1】:

这并不重要。标准在某些选定的地方使用 unsigned char,因为它允许在这些地方进行精确的表述:

  • fgetc 指定返回转换为 int 的无符号字符,以便知道结果是正数或空值,但 EOF 除外(因此 EOF 和有效字符之间不会混淆,当一个人直接将 fgetc 的结果存储在一个字符中而不事先检查 EOF 时,混淆是导致错误的原因)。

  • fputc 被指定为采用 int 并将其转换为 unsigned char,因为此转换已明确指定。如果你不小心,不使用 unsigned char 的公式可能会使 UB 像

    int c = fgetc(stdin);
    if (c != EOF)
        fputc(c, stdout);
    

负字符用有符号字符。

【讨论】:

  • 假设FILE的实现使用char作为流的基础,上面的代码将如何产生未定义的行为?比如fputc里面,字符要存到char x里面,那x = (unsigned char)cx = c不相等吗?
  • @Shahbaz,如果标准没有说明强制要求,那么它将是未定义的。并且 undefined 是未定义的,即使最明显的实现是明确定义的。无论好坏,实现往往会做一些不明显的事情,并利用标准中存在的任何宽容。 (顺便说一句,有符号的 char = int 可以发出信号,因此这里没有很好地定义明显的实现,但这是重点)。
  • signed char = int 可以发出信号,感谢澄清!
【解决方案2】:

这并不重要。 char 的长度为 CHAR_BIT 位(limits.h - 通常为 8 位),无论是否已签名。

这些函数适用于CHAR_BIT 位块,因此符号在此处对写入或读取过程没有影响。

然后,您可以通过适当地转换结果来使用有符号或无符号字符,具体取决于您的应用程序逻辑。人类表示会有所不同,具体取决于符号,但对于处理器来说,表示不会改变。它仍然是字节。

【讨论】:

  • char 不需要 8 位。它是至少 8 位。
  • 为什么标准不直接说 converted to char 而不是 converted to unsigned char
  • a charCHAR_BIT 位长(记住 #include <limits.h>)。
  • 正确。添加了 CHAR_BIT 而不是 8 位。
  • @Shahbaz 因为char 可以是有符号或无符号的,这取决于编译器。说unsigned char 消除了这里的歧义。
【解决方案3】:

您唯一可以直接观察(无需检查来源)的是 API 返回的内容。它背后的任何东西都被黑盒抽象所隐藏,不应该是你关心的问题。

关于您问题的另一部分:标准必须注意,有一个转换,因为参数/返回值是int 并且流是字符序列。

【讨论】:

  • 其实它我关心的。我正在编写(某种)port of stdio.h 作为内核模块,因此我需要完全标准并了解细节。如果您有兴趣,我这样做是为了解决我在this question 中提到的问题。
  • 如果您对 Linux 内核 内部的功能感兴趣,您应该问一个具体的问题...
  • 不,我对标准要求的内容感兴趣,但我说这是我关心的问题,因为我正在实现 stdio,它恰好位于 Linux 内核中。
  • 那我已经回答了你的问题。 C 标准没有规定这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-13
  • 1970-01-01
  • 1970-01-01
  • 2017-01-13
  • 2013-06-18
  • 2011-06-01
相关资源
最近更新 更多