fread / fwrite以大小和数量为参数的基本原理是什么?


96

我们在这里讨论了为什么fread和fwrite占用每个成员的大小,然后计数并返回读取/写入的成员数,而不仅仅是获取缓冲区和大小。我们能想到的唯一用途是,如果您想读/写一个数组数组,这些数组不能被平台对齐均匀地整除,因此已经被填充了,但是不能如此普遍以至于不能保证这种选择在设计中。

FREAD(3)

函数fread()从stream指向的流中读取每个长度为字节大小的nmemb数据元素,并将它们存储在ptr给定的位置。

函数fwrite()将每个长度为字节的nmemb数据元素写入stream指向的流,并从ptr给定的位置获取它们。

fread()和fwrite()返回成功读取或写入的项目数(即,不是字符数)。如果发生错误或到达文件末尾,则返回值是短项计数(或零)。


10
嘿,这是一个好问题。我一直想知道
Johannes Schaub-litb

Answers:


22

它基于fread的实现方式。

单一UNIX规范说

对于每个对象,应调用fgetc()函数进行大小调用,并将结果按读取顺序存储在与对象完全重叠的无符号字符数组中。

fgetc也有此注释:

由于fgetc()对字节进行操作,因此读取由多个字节组成的字符(或“多字节字符”)可能需要多次调用fgetc()。

当然,这早于像UTF-8这样的花哨的可变字节字符编码。

SUS指出,这实际上来自ISO C文档。


72

fread(buf,1000,1,stream)和fread(buf,1,1000,stream)的区别是,在第一种情况下,如果文件较小,则仅获得一个1000字节或nuthin的块。第二种情况是,文件中的所有内容都小于等于1000个字节。


4
虽然是正确的,但这只说明了故事的一小部分。最好对比一些读取的内容,例如一个int值的数组或一个结构的数组。
乔纳森·莱夫勒

3
如果理由成立,这将是一个很好的答案。
马特·乔纳

13

这纯粹是猜测,但是在过去(仍然存在),许多文件系统不是硬盘驱动器上的简单字节流。

许多文件系统都是基于记录的,因此为了有效地满足此类文件系统的要求,您必须指定项目数(“记录”),从而允许fwrite / fread作为记录而不是字节流作为记录在存储上进行操作。


1
我很高兴有人提出来。我对文件系统规范和FTP进行了大量工作,记录/页面和其他阻止概念得到了非常牢固的支持,尽管没有人再使用这些规范的那些部分。
马特·乔纳

9

在这里,让我修复这些功能:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

至于fread()/ 的参数的基本原理fwrite(),我很早以前就丢失了K&R的副本,因此只能猜测。我认为一个可能的答案是,Kernighan和Ritchie可能只是认为,执行二进制I / O将最自然地在对象数组上完成。同样,他们可能认为块I / O在某些架构上执行起来更快或更容易。

尽管C标准规定,fread()fwrite()在以下方面来实现fgetc()fputc(),记住标准应运而生下通过K&R和标准规定的威力没有的东西一直在原设计者思路确定后不久。甚至可能K&R的“ C编程语言”中所说的内容可能与最初设计该语言时的含义不同。

最后,这是PJ Plauger fread()在“标准C库”中必须说的话:

如果size(第二个)参数大于一个,则无法确定该函数是否还读取size - 1超出其报告内容的其他字符。通常,最好fread(buf, 1, size * n, stream);不要以代替 fread(buf, size, n, stream);

基本上,他是说fread()的接口已损坏。对于fwrite()他指出,“写错误一般都是罕见的,所以这不是一个主要的缺点” -我不会同意的声明。


17
实际上,我经常喜欢用另一种方式:fread(buf, size*n, 1, stream);如果读取不完整是一种错误情况,则安排fread返回0或1而不是读取的字节数更为简单。然后,您可以执行类似的操作,if (!fread(...))而不必将结果与请求的字节数进行比较(这需要额外的C代码和额外的机器代码)。
R .. GitHub停止帮助ICE,2010年

1
@R ..只需确保检查大小* count!= 0以及!fread(...)。如果size * count == 0,则在成功读取(零字节)时获得零返回值,不会设置feof()和ferror(),并且errno会像ENOENT那样荒谬,甚至更糟,诸如EAGAIN之类的具有误导性(甚至可能致命的破坏)的东西-非常令人困惑,尤其是因为基本上没有文档向您尖叫。
飞马Epsilon


1

对于可以避免读取任何部分记录的实现,对于大小和计数使用单独的参数可能是有利的。如果一个人使用管道之类的单字节读取,即使一个人使用的是固定格式的数据,则必须考虑到记录可能被两次读取分割的可能性。如果可以代替,例如,当有293个字节可用时,请求无阻塞读取多达40个记录(每个记录10个字节),并且让系统返回290个字节(29个完整记录),而为下一次读取保留3个字节,则将更加方便。

我不知道fread的实现可以在多大程度上处理这种语义,但是对于可以承诺支持它们的实现,它们当然可以派上用场。


@PegasusEpsilon:例如,如果某个程序确实执行fread(buffer, 10000, 2, stdin)了该操作,并且用户在键入18,000个字节后键入了newline-ctrl-D,那么该函数可以返回前10,000个字节,而剩余的8,000个未决以供将来进行较小的读取请求,则很好,但是那里有什么实现会发生在哪里?8,000个字节将存储在哪里,以待将来发出这些请求?
超级猫

刚测试完之后,结果发现fread()不能以我认为这方面最方便的方式运行,但是在确定短读取之后将字节填充回读取缓冲区可能比我们期望的要多反正标准库函数。fread()会读取部分记录并将它们推入缓冲区,但是返回值将指定已读取多少条完整记录,并且不会告诉您任何关于从stdin中进行的短读取的信息(这对我来说很烦人)。
飞马Epsilon

...续...最好的办法是在读取前用空值填充读取缓冲区,并在fread()说完成任何非空字节之后检查记录。当您的记录可能包含null时,对您没有特别的帮助,但是如果您要使用size大于1的记录,那么……对于该记录,还可能存在ioctl或其他废话,您可以将其应用于流以使其成为流表现不同,我还没有深入研究。
飞马Epsilon

另外,由于不准确,我也删除了我之前的评论。那好吧。
飞马Epsilon

@PegasusEpsilon:C在许多平台上使用,它们适应不同的行为。程序员应该在所有实现中使用相同的功能和保证的想法忽略了C的最佳功能:它的设计将允许程序员在可用的平台上使用功能和保证。某些类型的流可以轻松地支持任意大小的推回,fread如果有某种方式来标识以这种方式工作的流,那么按照您在此类流上所述进行工作将很有用。
超级猫

0

我认为这是因为C缺少函数重载。如果有的话,大小将是多余的。但是在C语言中,您无法确定数组元素的大小,必须指定一个。

考虑一下:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

如果fwrite接受了字节数,则可以编写以下内容:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

但这只是效率低下。您将得到sizeof(int)乘以更多的系统调用。

应该考虑的另一点是,您通常不希望将数组元素的一部分写入文件。您想要整个整数或什么都不想要。fwrite返回成功写入的许多元素。因此,如果发现仅写入一个元素的2个低字节,该怎么办?

在某些系统上(由于对齐),如果不创建副本并进行移位,就无法访​​问整数的一个字节。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.