fread/fwrite 将 size 和 count 作为参数的理由是啥?
Posted
技术标签:
【中文标题】fread/fwrite 将 size 和 count 作为参数的理由是啥?【英文标题】:What is the rationale for fread/fwrite taking size and count as arguments?fread/fwrite 将 size 和 count 作为参数的理由是什么? 【发布时间】:2010-09-22 16:46:17 【问题描述】:我们在这里讨论了为什么 fread() 和 fwrite() 为每个成员取一个大小,然后计算并返回读/写的成员数量,而不是而不仅仅是获取缓冲区和大小。我们能想到的唯一用途是,如果你想读/写一个结构数组,这些结构不能被平台对齐整除,因此已经被填充,但不能那么普遍,以至于不能保证这个选择在设计中。
来自fread(3):
函数 fread() 读取数据的 nmemb 元素,每个 size 字节长, 从流指向的流中,将它们存储在给定的位置 通过ptr。
函数 fwrite() 写入数据的 nmemb 元素,每个 size 字节 long,指向stream指向的流,从位置获取 由ptr给出。
fread() 和 fwrite() 返回成功读取或写入的项目数 (即,不是字符数)。如果发生错误,或 到达文件末尾,返回值是一个短的项目计数(或零)。
【问题讨论】:
嘿,这是个好问题。我一直想知道 请查看此主题:***.com/questions/8589425/how-does-fread-really-work 【参考方案1】:fread(buf, 1000, 1, stream)
和 fread(buf, 1, 1000, stream)
的区别在于,在第一种情况下,如果文件更小,您只会得到一个 1000 字节的块或什么都没有,而在第二种情况下,文件中的所有内容都小于和最多 1000 个字节。
【讨论】:
虽然是真的,但这只是故事的一小部分。最好对比读取的内容,例如 int 值数组或结构数组。 如果理由充分,这将是一个很好的答案。【参考方案2】:它基于fread 的实现方式。
单一 UNIX 规范说
对于每个对象,大小调用应为 对 fgetc() 函数和 结果按读取顺序存储在 一个无符号字符数组 覆盖对象。
fgetc也有这个说明:
由于 fgetc() 对字节进行操作, 读一个字符 多个字节(或“一个多字节 字符") 可能需要多次调用 到 fgetc()。
当然,这早于 UTF-8 等花哨的可变字节字符编码。
SUS 指出这实际上取自 ISO C 文档。
【讨论】:
【参考方案3】:这纯粹是推测,但是在过去(有些仍然存在),许多文件系统并不是硬盘驱动器上的简单字节流。
许多文件系统都是基于记录的,因此为了以有效的方式满足此类文件系统,您必须指定项目的数量(“记录”),允许 fwrite/fread 将存储作为记录进行操作,而不仅仅是字节流。
【讨论】:
我很高兴有人提出这个问题。我在文件系统规范和 FTP 以及记录/页面和其他阻塞概念方面做了很多工作,尽管没有人再使用规范的这些部分了。【参考方案4】:在这里,让我修复这些功能:
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 定义 C 之后很久才存在的,并且在标准可能不是最初设计者的想法。甚至有可能 K&R 的“The C Programming Language”中所说的内容可能与最初设计该语言时不同。
最后,以下是 P.J. Plauger 在“标准 C 库”中对 fread()
的评价:
如果
size
(第二个)参数大于一,则无法确定 该函数是否还读取了超出其报告内容的size - 1
额外字符。 通常,您最好将该函数调用为fread(buf, 1, size * n, stream);
而不是fread(buf, size, n, stream);
基本上,他是说fread()
的界面坏了。对于fwrite()
,他指出,“写入错误通常很少见,所以这不是主要缺点”——我不同意这种说法。
【讨论】:
其实我经常喜欢反其道而行之:fread(buf, size*n, 1, stream);
如果不完整的读取是一种错误情况,安排fread
简单地返回 0 或 1 而不是读取的字节数.然后您可以执行if (!fread(...))
之类的操作,而不必将结果与请求的字节数进行比较(这需要额外的 C 代码和额外的机器代码)。
@R.. 除了 !fread(...) 之外,请务必检查 size * count != 0。如果 size * count == 0,您在 成功 读取(零字节)时获得零返回值,不会设置 feof() 和 ferror(),并且 errno 将是像 ENOENT 这样荒谬的东西,或者更糟糕的是,像 EAGAIN 这样具有误导性(并且可能严重破坏)的东西 - 非常令人困惑,特别是因为基本上没有文档会向你发出这个问题。【参考方案5】:
这可能要追溯到文件 I/O 的实现方式。 (过去)分块写入/读取文件然后一次写入所有内容可能会更快。
【讨论】:
并非如此。 fwrite 的 C 规范指出它重复调用 fputc:opengroup.org/onlinepubs/009695399/functions/fwrite.html【参考方案6】:对于可以避免读取任何部分记录的实现,具有单独的大小和计数参数可能是有利的。如果要从管道之类的东西中使用单字节读取,即使使用的是固定格式的数据,也必须考虑到一条记录被拆分为两次读取的可能性。如果可以改为请求,例如当有 293 个字节可用时,非阻塞读取最多 40 个记录,每个记录 10 个字节,并让系统返回 290 个字节(29 个完整记录),同时为下一次读取准备 3 个字节,这样会方便得多。
我不知道 fread 的实现在多大程度上可以处理这种语义,但它们肯定可以在承诺支持它们的实现上派上用场。
【讨论】:
@PegasusEpsilon:如果例如一个程序执行fread(buffer, 10000, 2, stdin)
并且用户在键入 18,000 个字节后键入 newline-ctrl-D,如果该函数可以返回前 10,000 个字节而将剩余的 8,000 个字节留待未来较小的读取请求,那就太好了,但是有任何实现那会发生在哪里?这 8000 个字节将存储在哪里等待未来的请求?
刚刚对其进行了测试,结果发现 fread() 并没有以我认为在这方面最方便的方式运行,但是在确定短读取之后将字节填充回读取缓冲区可能是无论如何,比我们对标准库函数的期望要多一点。 fread() 将读取部分记录并将它们推入缓冲区,但返回值将指定已读取了多少 complete 记录,并且不会告诉您任何短消息(这对我来说相当烦人)读取标准输入。
...继续...您能做的最好的可能是在 fread 之前用空值填充您的读取缓冲区,并在 fread() 表示它完成任何非空字节之后检查记录。当您的记录可能包含 null 时对您没有特别的帮助,但是如果您要使用大于 1 的 size
,那么...作为记录,您可能还可以将 ioctls 或其他废话应用于流为了让它表现不同,我没有深入研究。
由于不准确,我也删除了我之前的评论。哦,好吧。
@PegasusEpsilon:C 在很多平台上使用,它们适应不同的行为。程序员应该期望在所有实现上使用相同的特性和保证的概念忽略了 C 的最佳特性:它的设计将允许程序员在可用的平台上使用特性和保证。某些类型的流可以轻松支持任意大小的推回,如果有某种方法可以识别以这种方式工作的流,那么让 fread
按照您在此类流上的描述工作将很有用。【参考方案7】:
我认为这是因为 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 个低字节,你会怎么做?
在某些系统上(由于对齐),如果不创建副本和移位,您无法访问整数的一个字节。
【讨论】:
以上是关于fread/fwrite 将 size 和 count 作为参数的理由是啥?的主要内容,如果未能解决你的问题,请参考以下文章
C语言 将一个磁盘文件中的信息复制到另一个磁盘文件中,要求使用 fread fwrite这两个函数来实现
对 STL 字符串使用 fread/fwrite。这是正确的吗?