Tho*_*her 4 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;
Run Code Online (Sandbox Code Playgroud)
令人困惑的是,宏中的条件实际上是测试结构的缓冲区是否已满(这在文中说明) - 作为旁注,getc中的条件完全相同但意味着缓冲区为空.奇怪的?
这是我需要澄清的地方:我认为putc中的缓冲写入存在一个相当大的问题; 因为写入p只在_flushbuf()中执行,但_flushbuf()仅在文件结构的缓冲区已满时调用,然后只有在缓冲区完全填满时才进行写入.缓冲读数的大小始终是系统的BUFSIZ.除了'BUFSIZ'字符之外的任何其他内容都不会发生,因为_flushbuf()永远不会在putc中调用.
putc适用于无缓冲写入.但是宏的设计使得缓冲写入几乎完全没有意义.这是正确的,还是我在这里遗漏了什么?为什么会这样?我非常感谢这里的所有帮助.
如果你写得足够多,缓冲区最终会满的。如果不这样做,您最终将关闭该文件(或者运行时将在main()返回时为您执行此操作)并fclose()调用_flushbuf()或其等效项。或者您将手动fflush()传输流,这也相当于_flushbuf().
如果您写几个字符然后调用sleep(1000),您会发现很长一段时间没有打印任何内容。这确实是它的工作原理。
getc 和 putc 中的测试是相同的,因为在一种情况下,计数器记录有多少个可用字符,在另一种情况下,它记录有多少可用空间。
我想你可能会误读putc()宏观中发生的事情; 那里有很多运算符和符号,它们都很重要(以及它们的执行顺序很重要!).为了更好地理解它,让我们将其替换为实际用法,然后将其展开直到您可以看到正在发生的事情.
让我们从简单的调用开始putc('a', file),如下例所示:
FILE *file = /* ... get a file pointer from somewhere ... */;
putc('a', file);
Run Code Online (Sandbox Code Playgroud)
现在替换宏来代替调用putc()(这是简单的部分,由C预处理器执行;另外,我认为你在你提供的版本末尾缺少一个括号,所以我要去将它插入它所属的末尾):
FILE *file = /* ... get a file pointer from somewhere ... */;
(--(file)->cnt >= 0 ? *(file)->ptr++ = ('a') : _flushbuf(('a'),file));
Run Code Online (Sandbox Code Playgroud)
好吧,不是一堆乱七八糟的符号.让我们去除不需要的括号,然后转换?...:成它实际上在引擎盖下的if语句:
FILE *file = /* ... get a file pointer from somewhere ... */;
if (--file->cnt >= 0)
*file->ptr++ = 'a';
else
_flushbuf('a', file);
Run Code Online (Sandbox Code Playgroud)
这是更接近的,但仍然不是很明显发生了什么.让我们将增量和减量移动到单独的语句中,这样就可以更容易地看到执行的顺序:
FILE *file = /* ... get a file pointer from somewhere ... */;
--file->cnt;
if (file->cnt >= 0) {
*file->ptr = 'a';
file->ptr++;
}
else {
_flushbuf('a', file);
}
Run Code Online (Sandbox Code Playgroud)
现在,随着内容重新排序,应该更容易看到发生了什么.首先,我们减少cnt剩余字符的数量.如果这表明还剩下空间,则可以安全地写入a文件的缓冲区,文件的当前写指针,然后我们将写指针向前移动.
如果没有剩余空间,那么我们调用_flushbuf(),传递它的文件(其缓冲区已满)和我们想写的字符但不能.据推测,_flushbuf()将首先将整个缓冲区写入实际的底层I/O系统,然后它将写入该字符,然后可能重置ptr为缓冲区的开头并cnt指向一个大数字以指示缓冲区能够存储再次提供大量数据.
那为什么这会导致缓冲写入呢?答案是,_flushbuf()当缓冲区已满时,呼叫只会"每隔一段时间"执行一次.将一个字节写入缓冲区很便宜,而执行实际I/O是很昂贵的,因此这会导致_flushbuf()相对较少调用(每个BUFSIZ字符只调用一次).