在文件末尾之前只有部分记录可用时 fread 的行为

Behavior of fread when only a partial record is available before the end of file

我正在研究可以被视为 C 解释器的东西,它可以检测它所解释的程序中的所有未定义行为。在使用此解释器查找遗留 open-source C 应用程序中的错误时,我对以下行为感到困惑:

遗留应用程序需要一个 10 字节 header,它需要完整的数据才能进行进一步的工作。它正确地调用了 fread(buffer, 10, 1, f);。错误地,它没有分配 fread 调用的结果,并立即开始解析缓冲区。

当此 fread 应用于可用数据少于 10 个字节的文件时,发生的情况是 buffer 被部分归档为可用数据。按照设计,解释器检测到缓冲区的未初始化部分稍后被使用并发出警告,我能够将问题追溯到 fread 被丢弃的结果。

同事写的 fread 虽然最终会 return 0 ,但它已经部分填充了缓冲区,这让我困惑了一分钟,我想知道是否这可以改进。显然有些实现会读入缓冲区,最后 return 读取记录的数量为 return n_bytes / __size;,让除法向下舍入,在这种情况下为 0。但我想知道如果整个记录可用,其他实现是否可能只写入 buffer,否则完全未初始化。

实际上,在我手边的两个 Unice 上,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

测试程序也在 Linux 上使用 Glibc 产生相同的结果(文件 aaaa 包含 aaaa)。

分析在“fread 部分填充最后一条记录”和“fread 在缓冲区中保留最后一条部分记录不变”的每种情况下发生的情况的成本会高得不合理解释器,但我们可以使缓冲区的一部分应该接收到只有部分数据未初始化的记录,以防止解释的 C 程序依赖它。但是当 fread 传递一个已经初始化的缓冲区时,这又会令人费解。

所以我发现自己想知道解释器的 fread 当前具有的行为以及 macOS 和 Linux/Glibc 也具有的行为是否得到保证(在这种情况下一切都很好)。

我发现 . 似乎表明我在所有三种实现中观察到的 fread 行为是唯一可能的行为,但我想要明确确认(或不确认)fread 可以假定读取所有可用字符直到 size * nmemb,即使可用字符数不是 size.

的倍数

两个 C99 (§7.19.8.2) and C11 (§7.21.8.2) 定义 fread() 具有以下描述:

The fread function reads, into the array pointed to by ptr, up to nmemb elements whose size is specified by size, from the stream pointed to by stream. For each object, size calls are made to the fgetc function and the results stored, in the order read, in an array of unsigned char exactly overlaying the object. The file position indicator for the stream (if defined) is advanced by the number of characters successfully read. If an error occurs, the resulting value of the file position indicator for the stream is indeterminate. If a partial element is read, its value is indeterminate.

最后一点应该可以消除您的疑虑:

If a partial element is read, its value is indeterminate.

因此,即使您看到的所有实现都表现得“很好”,您也不能依赖它,因为它是标准未定义的依赖于实现的行为。希望读取部分元素(如果文件包含一个)的程序应该使用 1size 并检查 return 值。事实上,如果一个程序不这样做而是用 size > 1nmemb = 1 读取,它甚至无法在最后一部分的情况下区分缓冲区中的初始化数据和未初始化数据元素.