随笔-90  评论-947  文章-0  trackbacks-0

昨天晚上和同事讨论写 Log 的问题,谈到写到文件,后来谈到写文件用 ReadFile、WriteFile 还是用 fread、fwrite 的问题。我一直对 fread、fwrite 没啥好感,原因是它自作主张的搞了一套缓存机制。可是仅仅这点就鄙视它似乎还说不过去。谈着谈着,后来我们对它的参数设计起了怀疑——这里有一个参数是多余的!从表面看,ReadFile、WriteFile 的参数是恰到好处的,fread、fwrite 作为它们的上层函数,似乎没必要把一个参数拆成 2 个呀。

后来就一直跟 fread,直到出现 ReadFile,都没发现这 2 个参数有什么特别的用处,他们很早就被乘起来了:

count = total = elementSize * count;

所以,目前我仍然对这个设计感到困惑。

有谁知道,这是由于什么样的历史原因/技术原因,才使这个函数变成现在这副模样的?

posted on 2010-04-04 19:41 溪流 阅读(5294) 评论(35)  编辑 收藏 引用 所属分类: C++

评论:
# re: fread、fwrite 的参数设计问题[未登录] 2010-04-04 23:09 | Fox
没有具体测试过,fwrite用于写结构、二进制数组,可以参考w.r.stevens在apue(5.9, p145)中关于fwrite的描述。  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 00:45 | OwnWaterloo
fread和fwrite是C标准库, Read/WriteFile是Windows API。
如果不需要HANLDE的特殊功能, 使用fread/fwrite的代码可以不加修改的移植到其他平台去。

参数用于错误处理。

T a[count];
if ( count != fread(a, sizeof a[0], count, stream) ) {
// error
}

if ( count != READ(a, sizeof a, stream )/sizeof a[0] ) {
// error
}
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 00:52 | 溪流
@OwnWaterloo
我是说它参数里头的 elementSize 和 count,应该合并成一个,没必要分成两个  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 00:53 | 溪流
@OwnWaterloo
直接传入需要读取的字节数,传出已成功读取的字节数  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 01:00 | OwnWaterloo
@溪流
下面的READ就是按你说的方式设计的。
你觉得用着方便吗?
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 08:49 | 溪流
@OwnWaterloo
下面的好。可是为什么上面的当初要那样设计呢?  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 15:40 | OwnWaterloo
@溪流
呃。。。 下面不是还要做一次除法么。。。
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 17:06 | 溪流
@OwnWaterloo
不理解
我的意思是,fread应该这样:
int fread(FILE *file, unsigned char *buffer, int bufferSize);
返回已读取的字节数
哪里来的一个size加有一个count  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 17:17 | OwnWaterloo
@溪流
凭什么只能是unsigned char*呢?
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 18:10 | 溪流
@OwnWaterloo
void * 也行
读写文件我应该知道要读/写多少字节吧?原先的第二个参数可以固定为1  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 18:36 | OwnWaterloo
@溪流
你想得太狭隘了。

仔细看2楼的例子。 T非得是char吗?
a非得是数组吗?

T a[1]; 可以吗?
T v;可以吗?
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 18:49 | 溪流
@OwnWaterloo
T a[count];
if ( count != fread(a, sizeof a[0], count, stream) ) {
// error
}

T a[count];
ReadFile(hFile, a, sizeof(a), ...);
这样多明确啊!  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 18:58 | OwnWaterloo
@溪流
2楼仔细看。 我懒得说了。
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 20:52 | 溪流
@OwnWaterloo
看不来了,你解释一下吧
我不要返回count,返回bytesRead就行了  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 21:08 | OwnWaterloo
@溪流
你不需要不等于别人不需要……

你完全将ReadFile或者fread当作"字节流读取"来使用了。
实际上,fread不仅仅可以用来读取字节流;
还可以用来读取"二进制格式存放的数据", 比如上面的T。

  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 21:27 | 溪流
@OwnWaterloo
本来就是字节流嘛
“二进制格式存放的数据”,无非也就是多少个字节而已。
1个T就是 sizeof(T) 个 byte  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 22:05 | OwnWaterloo
@溪流
读出半个T怎么办?
还没看懂2楼的代码?
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 23:25 | 溪流
@OwnWaterloo
那应该告诉我读了 sizeof(T)/2 个字节,而不是告诉我读了 0 个 T 啊  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 23:28 | 溪流
不知道二楼的代码有什么精妙之处,,请解释下。。。。
T a[count];
if ( count != fread(a, sizeof a[0], count, stream) ) {
// error
}

这段完全可以写成:
T a[count];
if ( bytes != fread(a, 1, (sizeof a[0]) * count, stream) ) {
// error
}
么,而且能够知道到底读了多少,半个T,1/4个T,都可以精确表示出来
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-05 23:54 | OwnWaterloo
@溪流
>>那应该告诉我读了 sizeof(T)/2 个字节,而不是告诉我读了 0 个 T 啊

你已经形成思维定势了:
1. fread应该设计成什么样子, 你心中已经有答案
2. fread如果设计成那样子我会怎么用, 你已经习惯这么用
3. 应为我习惯这么用, 所以fread设计成这样就是不对的

这是循环论证, 说明不了任何问题。


现在你要抛弃你的习惯:
习惯1: 不仅仅用来读取字节流, 还可以直接读取二进制格式文件
习惯2: 有返回值检测


然后比较两段代码, 看哪段写着顺手:

void f(T* buf, size_t buf_size, FILE* stream)
{
size_t count = fread(buf, sizeof *buf, buf_size, stream);
for (size_t i=0; i<count; ++i)
process(buf[i ] );
}

void g(T* buf, size_t buf_size, FILE* stream)
{
size_t bytes = Read(buf, sizeof(T) * buf_size, stream);
for (size_t i=0; i< bytes/ sizoef(T); ++i)
process( buf[i ] );
}
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 00:14 | 溪流
@OwnWaterloo
Read 那一句我习惯第二段的。
不错,是读取二进制文件格式,文件格式应该已经知晓了。何谓知晓?就是知道它每一个字节是干啥的,所以精确到每个字节不带来坏处。

假如有个文件头(为讨论方便,请忽略对齐问题)
struct T
{
BYTE sign[10];
DWORD headerSize;
。。。
}

你是写成 read(buf, sizeof(T), 1, stream) 呢,还是写成 read(buf, 1, sizeof(T), stream) 呢?还是觉得这个 1 是多余的?

按你说的最小设计原则,不用 elememtSize * count 的形式,直接精确到字节,现有的任务都可以完成。而搞成 elementSize * count 的形式,在挺大一部分的例子中,有个参数是多余的。那么,这个设计不是很失败么?

第一点,我不觉得“字节流”与“二进制文件”有什么区别
第二点,要返回值检测,不至于我连读到哪儿了都不知道  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 10:48 | 欣萌
2楼说的即是  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 11:18 | 陈梓瀚(vczh)
古时候人们读的是struct不是char,所以纯粹是为了增加可读性……  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 12:00 | 欣萌
恩 我其实觉得 也没有必要有2个参数
size 和 count


反正都是读size*count个。

反正我没有看懂,OwnWaterloo 说的理由。

  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 16:59 | OwnWaterloo
@溪流
对, 你说得对。
fread确实干了多余的事情。

调用时那个乘法无所谓, 要么就是调用者自己做, 要么就是fread做。
问题出在返回时那个除法上。 fread总会做一个除法, 对很多情况是不需要的。
而不使用乘/除法实现的fread又需要大量的循环。
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 17:14 | OwnWaterloo
@溪流
跟"最小接口"与"人本接口"没什么关系。
相对于read_int, read_string, 这两种设计都可以算是最小接口。


为了不降低"实现优化的潜力", 保留适当的信息对实现是有用的。
比如malloc vs calloc。
用户端的信息原本是 sizeof(elem) 和 count两个。
calloc会将信息继续带到下层。
而malloc将这个信息丢掉了。

这可能就会影响malloc的某种优化, 比如对齐策略。
1. 分配12个char的数组
2. 分配12/sizeof(int)个数组
对malloc来说, 它接受到的都是12bytes, 已经无法区分这两者的不同。
malloc始终返回的是该平台下有最严格对齐的内存。

而calloc就可以明确知道每个元素的大小, 可以利用这个信息, 计算出该种大小的所有类型在该平台下的最严格对齐需求,就可以少填充一些。

当然, calloc不一定会使用这个信息进行优化, 但它保有这个优化的潜力。
(calloc另一个memset的行为就很恶心了)


而read, 没想出可以利用size和count进行优化的方法。
而且将这2个参数分开, 同时又要避免返回时那个除法的话, 还会显得不够统一。
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-06 17:36 | Jakcie
同意@OwnWaterloo 的看法。
还是2个参数确定读取字节数比较好。  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-09 03:05 | 欲三更
你不觉得把读取的每个单位的尺寸和单位数分开可读性更高么?一个函数调用的实参中出现了乘法,第一眼看上去肯定会觉得费解。

而且往高里说这是个设计理念问题,作为一个接口,让用户越少思考越好。

sizeof(XXX) * count = 总字节数,这是你想出来的,想这个问题难道不需要费力么?  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-09 09:26 | 溪流
@欲三更
有什么费力的?都到读写文件的关口了,难道几个字节你都不想弄清楚?
另,你喜欢写成 fread(buf, sizeof(T), 1, stream) 呢,还是 fread(buf, 1, sizeof(T), stream) 呢?每次我都为这个问题纠结好久。  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-04-09 15:09 | OwnWaterloo
@溪流
你这个问题很好解决~
找个现代点的ide...
  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2010-05-01 01:45 | hoodlum1980
参数设计的很好啊,一个数据块的单位大小,一个是要读取多少个数据块,返回读取成功的数据块数目。这个设计很好呀。你可以尝试作为一个整块式的读取。  回复  更多评论
  
# re: fread、fwrite 的参数设计问题[未登录] 2011-03-11 09:47 | C++爱好者
@欲三更
如果连这个代码的意思都看不懂那还是不要写代码算了!!!  回复  更多评论
  
# re: fread、fwrite 的参数设计问题[未登录] 2011-03-11 09:52 | C++爱好者
@溪流
看来你也是个完美主义者,当然是fread(buf, sizeof(T), 1, fp)咯,这才是这个函数接口的本意。但我也觉得这个参数有点多余!!!  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2011-03-12 22:32 | 溪流
@C++爱好者
呵呵。重新回顾了上面这么多纠结的提法(块啊、字节流啊、二进制格式啊、struct啊,char啊),再看到你说明白话,顿感轻松。块也好、二进制格式也好、struct也好,不就是一堆字节么。。。  回复  更多评论
  
# re: fread、fwrite 的参数设计问题 2011-06-01 12:50 | DH
正好要用,谢谢  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理