了解K&R的putc宏:K&R第8章(Unix系统接口)练习2

时间:2018-07-23 19:33:34

标签: c macros file-writing

一段时间以来,我一直在试图了解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可以很好地用于无缓冲写入。但是宏的设计使缓冲写入几乎毫无意义。这是正确的,还是我在这里遗漏了一些东西?为什么会这样呢?我真的很感谢这里的所有帮助。

2 个答案:

答案 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中的测试是相同的,因为在一种情况下,计数器记录了多少个可用字符,而在另一种情况下,它记录了有多少可用空间。