了解 K&R 的 putc 宏:K&R 第 8 章(Unix 系统接口)练习 2
Understanding K&R's putc macro: K&R Chapter 8 (The Unix System Interface) Exercise 2
一段时间以来,我一直在尝试了解 K&R 的 putc 版本,但资源不足(google、堆栈溢出、clcwiki 不完全符合我的要求而且我没有朋友或同事可以求助)。我会先解释上下文,然后再要求澄清。
本章课文介绍了一个描述文件的数据结构的例子。该结构包括一个字符缓冲区,用于一次读取和写入大块。然后他们要求 reader 编写标准库 putc 的一个版本。
作为 reader 的线索,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 中缓冲写入有一个很大的问题;由于写入 p 仅在 _flushbuf() 中执行,而 _flushbuf() 仅在文件结构的缓冲区已满时调用,因此仅在缓冲区完全填满时才进行写入。缓冲读取的大小始终是系统的 BUFSIZ。除了完全 'BUFSIZ' 个字符之外,不会写入任何内容,因为 _flushbuf() 永远不会在 putc.
中调用
putc 对于无缓冲写入来说工作得很好。但是宏的设计使得缓冲写入几乎完全没有意义。这是正确的,还是我在这里遗漏了什么?为什么会这样?我真的很感谢这里的所有帮助。
如果你写得足够多,缓冲区最终会变满。如果不这样做,您最终将关闭文件(或者运行时会在 main()
returns 时为您完成)并且 fclose()
调用 _flushbuf()
或其等效项。或者您将手动 fflush()
流,这也相当于 _flushbuf()
.
如果你写几个字符然后调用sleep(1000)
,你会发现很长一段时间没有打印任何东西。这确实是它的工作方式。
getc 和 putc 中的测试是相同的,因为在一种情况下,计数器记录有多少字符可用,而在另一种情况下,它记录有多少 space 可用。
我认为您可能误读了 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()
调用仅在缓冲区已满时执行 "every once in a while,"。将一个字节写入缓冲区很便宜,而执行实际的 I/O 却很昂贵,因此这导致 _flushbuf()
被调用的次数相对较少(每 BUFSIZ
个字符仅调用一次)。
一段时间以来,我一直在尝试了解 K&R 的 putc 版本,但资源不足(google、堆栈溢出、clcwiki 不完全符合我的要求而且我没有朋友或同事可以求助)。我会先解释上下文,然后再要求澄清。
本章课文介绍了一个描述文件的数据结构的例子。该结构包括一个字符缓冲区,用于一次读取和写入大块。然后他们要求 reader 编写标准库 putc 的一个版本。
作为 reader 的线索,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 中缓冲写入有一个很大的问题;由于写入 p 仅在 _flushbuf() 中执行,而 _flushbuf() 仅在文件结构的缓冲区已满时调用,因此仅在缓冲区完全填满时才进行写入。缓冲读取的大小始终是系统的 BUFSIZ。除了完全 'BUFSIZ' 个字符之外,不会写入任何内容,因为 _flushbuf() 永远不会在 putc.
中调用putc 对于无缓冲写入来说工作得很好。但是宏的设计使得缓冲写入几乎完全没有意义。这是正确的,还是我在这里遗漏了什么?为什么会这样?我真的很感谢这里的所有帮助。
如果你写得足够多,缓冲区最终会变满。如果不这样做,您最终将关闭文件(或者运行时会在 main()
returns 时为您完成)并且 fclose()
调用 _flushbuf()
或其等效项。或者您将手动 fflush()
流,这也相当于 _flushbuf()
.
如果你写几个字符然后调用sleep(1000)
,你会发现很长一段时间没有打印任何东西。这确实是它的工作方式。
getc 和 putc 中的测试是相同的,因为在一种情况下,计数器记录有多少字符可用,而在另一种情况下,它记录有多少 space 可用。
我认为您可能误读了 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()
调用仅在缓冲区已满时执行 "every once in a while,"。将一个字节写入缓冲区很便宜,而执行实际的 I/O 却很昂贵,因此这导致 _flushbuf()
被调用的次数相对较少(每 BUFSIZ
个字符仅调用一次)。