c语言 - 理解 K&R 的 putc 宏:K&R 第 8 章(Unix 系统接口)练习 2



我一直在努力理解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的写入,但只有在文件结构的缓冲区已满时才会调用_flushbub((,因此只有在缓冲区已完全满时才执行写入。缓冲读取的大小始终是系统的BUFSIZ。写除"BUFSIZ"字符之外的任何字符都不会发生,因为在putc中永远不会调用_flushbuf((。

putc对无缓冲的写作很有效。但是宏的设计使得缓冲写入几乎毫无意义。这是正确的吗,还是我遗漏了什么?为什么是这样?我真的很感激这里的任何帮助。

我认为您可能误解了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()将首先将整个缓冲区写入实际的底层I/O系统,然后写入该字符,然后可能将ptr重置为缓冲区的开头,将cnt重置为一个大数字,以表明缓冲区能够再次存储大量数据。

那么,为什么这会导致缓冲写入呢?答案是_flushbuf()调用只在缓冲区满时"每隔一段时间"执行一次。将字节写入缓冲区成本较低,而执行实际I/O成本较高,因此这导致_flushbuf()被调用的次数相对较少(每BUFSIZ个字符仅调用一次(。

如果写得足够多,缓冲区最终会满。如果不这样做,您将最终关闭该文件(或者当main()返回时,运行库将为您执行此操作(,fclose()将调用_flushbuf()或其等效文件。或者您将手动fflush()流,这也相当于_flushbuf()

如果你写几个字符,然后调用sleep(1000),你会发现在很长一段时间内什么都不会打印出来。这确实是它的工作方式。

getc和putc中的测试是相同的,因为在一种情况下,计数器记录有多少字符可用,而在另一种情况中,它记录有多少空间可用。

最新更新