Pas*_*uoq 4 c fread language-lawyer
我的工作可以看作是一个 C 解释器,它检测它解释的程序中的所有未定义行为。在使用此解释器查找遗留开源 C 应用程序中的错误时,我对以下行为感到困惑:
遗留应用程序需要一个 10 字节的标头,它需要完整的标头才能做进一步的工作。它正确地调用了fread(buffer, 10, 1, f);. 错误的是,它没有分配fread调用的结果,并立即开始解析缓冲区。
当这fread应用于buffer可用数据少于 10 个字节的文件时,发生的情况是部分文件与可用数据一起归档。解释器,正如它设计的那样,检测到缓冲区的一个未初始化的部分后来被使用并警告了这一点,我能够将问题追溯到fread被丢弃的结果。
一分钟让我困惑的fread是,我同事写的部分填充了缓冲区,即使它0最终会返回,我想知道这是否可以改进。显然,一些实现确实读入缓冲区,最后将读取记录的数量返回为return n_bytes / __size;,让除法向下舍入,在这种情况下为0。但我想知道其他实现是否可能只buffer在整个记录可用时写入,否则完全未初始化。
实际上,在我手头的两个 Unices 上,fread其行为方式与我同事编写的模型实现方式相同:
~ $ cat t.c
#include <stdio.h>
#include <stdlib.h>
char buffer[11] = "0000000000";
int main(void) {
FILE *f = fopen("aaaa", "r");
if (!f) exit(1);
int r = fread(buffer, 10, 1, f);
printf("%s\n", buffer);
}
~ $ gcc t.c && ./a.out
aaaa000000
~ $ uname -a
Darwin tis-laptop-6.local 16.7.0 Darwin Kernel Version 16.7.0: Sun Jun 2 20:26:31 PDT 2019; root:xnu-3789.73.50~1/RELEASE_X86_64 x86_64
Run Code Online (Sandbox Code Playgroud)
测试程序在带有 Glibc 的 Linux 上也产生相同的结果(文件aaaa包含aaaa)。
在解释器中分析在每种情况下“fread部分填充最后一条记录”和“fread将最后部分记录保留在缓冲区中保持不变”的情况下发生的事情的成本是不合理的,但我们可以使缓冲区中应该接收的部分只有部分数据未初始化的记录,以防止解释的 C 程序依赖它。但是当fread传递一个已经初始化的缓冲区时,这又会令人费解。
所以我发现自己想知道解释器fread当前具有的行为以及 macOS 和 Linux/Glibc 也具有的行为是否得到保证(在这种情况下一切都很好)。
我发现了这个相关的问题。这条评论似乎表明fread我在所有三个实现中观察到的行为是唯一可能的,但我想要一个明确的确认(或不)fread可以假设读取所有可用字符,size * nmemb即使可用字符数不是 的倍数size。
两个C99(§7.19.8.2)和C11(§7.21.8.2)限定fread()与以下的说明中:
该
fread函数从 所指向的流中读入由 所指向的数组ptr,直到nmemb大小由 指定的元素为止。对于每个对象,都会调用该函数并将结果按读取顺序存储在一个完全覆盖该对象的数组中。流的文件位置指示符(如果已定义)按成功读取的字符数前进。如果发生错误,则流的文件位置指示符的结果值是不确定的。如果读取了部分元素,则其值是不确定的。sizestreamsizefgetcunsigned char
最后一点应该可以消除您的疑虑:
如果读取了部分元素,则其值是不确定的。
因此,即使您看到的所有实现看起来都表现得“很好”,但您不能依赖它,因为标准没有定义依赖于实现的行为。希望读取部分元素(如果文件包含一个)的程序应该使用sizeof1并检查返回值。实际上,如果程序不这样做而是使用size > 1and读取nmemb = 1,则在最后部分元素的情况下,它甚至无法区分进入缓冲区的初始化数据和未初始化数据。