一段时间以来,我一直在试图了解K&R的putc版本,但是我资源不足(谷歌,堆栈溢出,clcwiki没有我想要的东西,我没有朋友或同事求助)。我将先解释上下文,然后再进行澄清。
本文的这一章介绍了描述文件的数据结构的示例。该结构包括一个字符缓冲区,用于一次读取和写入大块。然后,他们要求读者编写标准库putc的版本。
作为读者的线索,K&R编写了getc版本,该版本同时支持缓冲读取和非缓冲读取。他们还编写了putc宏的框架,使用户自己编写了_flushbuf()函数。 putc宏看起来像这样(p是指向文件结构的指针):
int _flushbuf(int, FILE *);
#define putc(x,p) (--(p)->cnt >= 0 \
? *(p)->ptr++ = (x) : _flushbuf((x),p)
typedef struct {
int cnt; /*characters left*/
char *ptr; /*next character position*/
char *base; /*location of buffer*/
int flag; /*mode of file access*/
int fd; /*file descriptor*/
} FILE;
令人困惑的是,宏中的条件实际上是在测试结构的缓冲区是否已满(这在文本中有说明)-附带说明,getc中的条件完全相同,但意味着缓冲区为空。奇怪吗?
这是我需要澄清的地方:我认为putc中的缓冲写入存在很大的问题;由于仅在_flushbuf()中执行对p的写入,但是仅在文件结构的缓冲区已满时才调用_flushbuf(),因此仅在缓冲区完全填充时才进行写入。缓冲读取的大小始终是系统的BUFSIZ。只写'BUFSIZ'字符以外的任何东西都不会发生,因为_flushbuf()永远不会在putc中调用。
putc可以很好地用于无缓冲写入。但是宏的设计使缓冲写入几乎毫无意义。这是正确的,还是我在这里遗漏了一些东西?为什么会这样呢?我真的很感谢这里的所有帮助。
答案 0 :(得分:5)
我认为您可能误解了putc()
宏中发生的情况;那里有很多运算符和符号,并且它们都非常重要(它们的执行顺序也很重要!)。为了更好地理解它,让我们将其替换为实际用法,然后将其扩展直到可以看到发生了什么。
让我们从简单调用putc('a', file)
开始,如下例所示:
FILE *file = /* ... get a file pointer from somewhere ... */;
putc('a', file);
现在用宏代替对putc()
的调用(这是简单的部分,由C预处理程序执行;而且,我认为您在版本末尾缺少括号)提供,所以我要在它所属的末尾插入它:
FILE *file = /* ... get a file pointer from somewhere ... */;
(--(file)->cnt >= 0 ? *(file)->ptr++ = ('a') : _flushbuf(('a'),file));
嗯,不是一堆乱七八糟的符号。让我们去掉不需要的括号,然后将?...:
转换成if语句,它实际上是在幕后:
FILE *file = /* ... get a file pointer from somewhere ... */;
if (--file->cnt >= 0)
*file->ptr++ = 'a';
else
_flushbuf('a', file);
这更近了,但仍不是很明显。让我们将增量和减量移动到单独的语句中,以便更轻松地查看执行顺序:
FILE *file = /* ... get a file pointer from somewhere ... */;
--file->cnt;
if (file->cnt >= 0) {
*file->ptr = 'a';
file->ptr++;
}
else {
_flushbuf('a', file);
}
现在,随着内容的重新排序,应该更容易看到正在发生的事情。首先,我们减少cnt
,即剩余字符数。如果那表明还有剩余空间,则可以安全地将a
写入文件的当前写入指针处的文件缓冲区中,然后将写入指针向前移动。
如果没有个剩余空间,则我们调用_flushbuf()
,将文件(缓冲区已满)和想要写入但不能写入的字符传递给_flushbuf()
。大概,ptr
首先将整个缓冲区写出到实际的基础I / O系统中,然后将其写入该字符,然后很可能将cnt
重置为缓冲区的开头,然后{{1 }},以表示缓冲区能够再次存储大量数据。
那为什么会导致缓冲写入?答案是,_flushbuf()
调用仅在缓冲区已满时“偶尔执行”。将字节写入缓冲区很便宜,而执行实际的I / O则很昂贵,因此这导致_flushbuf()
的调用相对较少(每BUFSIZ
个字符仅调用一次)。
答案 1 :(得分:4)
如果写的足够多,缓冲区最终将变满。如果不这样做,最终将关闭文件(或者在main()
返回时运行时将为您执行该操作),并且fclose()
调用_flushbuf()
或等效文件。或者,您将手动fflush()
流,它也等效于_flushbuf()
。
如果要写几个字符然后调用sleep(1000)
,您会发现很长一段时间都没有打印任何内容。确实是它的工作方式。
getc和putc中的测试是相同的,因为在一种情况下,计数器记录了多少个可用字符,而在另一种情况下,它记录了有多少可用空间。