stdout行缓冲和stderr无缓冲的含义

stdio缓冲机制解析
本文详细解释了stdio中的stdout和stderr的缓冲机制,包括全缓冲、行缓冲和无缓冲的区别及如何通过标准库函数进行控制。

stdout和stderr

有人说stdio是带缓冲的,stderr是不带缓冲的,这并不是指fd=1和fd=2这两个设备文件,这两个设备是字符设备,本身没有缓存。并且你看一个进程的1和2两个fd指向的其实是同一个终端设备文件:

[root@ubuntu]arm-code:$ ls -l /proc/8669/fd/
total 0
lrwx------ 1 root root 64  425 20:57 0 -> /dev/pts/7
lrwx------ 1 root root 64  425 20:57 1 -> /dev/pts/7
lrwx------ 1 root root 64  425 20:57 2 -> /dev/pts/7

所以,细想一下就知道,向1或2两个fd写东西,在内核里走的是完全相同的路径,不可能存在一会儿缓存一会儿不缓存的情况。

那上面说的“缓存”到底是什么东西呢?

如果你调用printf()或fwrite(…, stdout),进入标准库后会先把要写的内容放到一个缓存里,直到遇到回车(或缓存满,比如缓冲最大1024字节)或者程序退出(return和exit()会刷stdout缓存),才会调用write系统调用进到内核设备驱动实际去写。这样可以降低write系统调用的频率,而向stderr写东西就不在标准库里做缓存而是立即调用write去写了。我们说stdout是行缓冲的,stderr是无缓冲的,就是这个意思,注意这里的stdout和stderr是指标准库里的FILE结构的指针,而不是标准输出和标准错误设备

所以下面的程序,每个字符串都没有加回车,你就会看到先打印”ddddd”,再一起打印”aaaaabbbbbccccc”。换成printf/perror或fprintf(stdout, …)/fprintf(stderr, …)也是一样的结果。而你用write(1, …)和write(2, …)都是直接调用系统调用进内核,每次都会立即打印出来。

int main()
{
    fwrite("aaaaa", 5, 1, stdout);
    fwrite("bbbbb", 5, 1, stdout);
    fwrite("ccccc", 5, 1, stdout);
    fwrite("ddddd", 5, 1, stderr); //stderr

    return 0;
}

缓冲类型

标准库中的stream(即FILE结构的流文件)有三种缓冲类型:
全缓冲(block buffered):以缓冲大小为限,例如4096字节,则你fwrite到4KB后标准库才会调用一次write去写。
行缓冲(line buffered):以换行为限,当遇到newline的时候标准库才会调用一次write去写。不过如前面所说,行缓冲区也有大小限制,例如1024字节,缓冲满了也会去write。
无缓冲(unbuffered):在标准库中不开辟缓冲区。

你也可以主动去清缓冲,使用fflush()即可:

#include <stdio.h>
int fflush(FILE *stream);

对于输出流(例如向stdio或其他文件写东西),fflush(stream)将文件在用户态标准库的缓冲全部刷到内核里。对输入流,fflush(stream)则丢弃所有尚未被应用程序拿到的缓冲数据。
如果参数stream为NULL,则fflush对所有打开的输出流文件做刷缓冲操作。

注:调用fclose()关闭文件前也会调用fflush()刷用户态缓冲的,并释放缓冲区内存(fopen()打开文件的时候,会为这个fp申请缓冲用的空间,如4KB,每个fp有自己的缓冲区)。
另外我实验exit()和return退出程序后也会清所有打开文件的缓冲区,而_exit()不会。

默认情况下,从终端的输入输出流都是行缓冲的,体现在诸如printf()/getchar()/putchar()等函数;stderr总是无缓冲的;其他文件流是全缓冲的。

你可以通过setvbuf()等函数修改stream的缓冲类型和缓冲区大小:

#include <stdio.h>
void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

-
再补充一点,我们目前提到的都是用户态的文件缓冲,flush之后能保证将写入内容提交给了内核。但对于块设备文件系统中的文件,仍不保证能真正写到磁盘文件中,因为内核的page cache还会做一层缓存。

<think>好的,我现在需要理解用户的问题:stdout缓冲以及描述符之间的关系,还有它们的作用机制。根据用户提供的引用资料,我需要整合这些信息,给出一个清晰的解释。 首先,用户提到了几个引用,其中引用[1]指出stdout对应文件描述符1,perror使用stderr(描述符2)。引用[3][4]讨论了缓冲区的类型,特别是缓冲在遇到换符时刷新。引用[4]中的例子说明,当关闭文件描述符1后,打开新文件会占用该描述符,此时printf的内容可能不会立即写入,需要fflush。这涉及到缓冲区的机制文件描述符的关系。 我需要分步骤来理清这些概念。首先,文件描述符是什么?在Unix系统中,文件描述符是一个整数,代表进程打开的文件。stdout对应的描述符是1,通常指向终端。缓冲是标准I/O库中的一种缓冲模式,当遇到换符时,缓冲区会被刷新,内容写入到对应的文件中,这里就是通过描述符1写入终端。 当程序使用printf时,数据首先被写入stdout缓冲区。如果是缓冲模式,遇到换符或缓冲区满时,会调用write系统调用,将数据通过文件描述符1写入。如果此时文件描述符1被重定向到其他文件(比如通过close(1)后打开新文件),那么数据会写入到新文件中,但缓冲区的刷新机制仍然依赖于缓冲的设置。例如,在引用[4]的例子中,关闭1后打开log.txt,此时printf的数据应该进入stdout缓冲区,但由于换符的存在,应该触发刷新。但实际结果可能需要调用fflush,这可能是因为重定向后的文件描述符是否继承缓冲策略的问题? 可能的情况是,当文件描述符1被重定向到普通文件时,缓冲模式可能从缓冲变为全缓冲,导致不会在换时自动刷新。这需要确认标准I/O库的缓冲策略。根据C标准,当stdout连接到终端时,通常是缓冲;如果连接到文件,则是全缓冲。所以在引用[4]的例子中,虽然文件描述符1指向了log.txt,但stdout缓冲模式可能变为全缓冲,因此printf的内容不会在换时自动刷新,需要手动fflush。 此外,文件描述符FILE结构体的关系也很重要。FILE结构体(如用户提供的myStdio.h中的FILE_结构)封装了文件描述符缓冲区信息。当调用fwrite_或printf时,数据先进入缓冲区,根据刷新策略决定何时调用write系统调用,使用对应的文件描述符进写入。 总结起来,stdout缓冲机制(缓冲)与文件描述符1相关联,但缓冲模式可能根据文件类型变化。当描述符1指向终端时,缓冲生效;指向普通文件时,可能变为全缓冲。此时,即使有换符,也不会自动刷新,需要fflush。这解释了为什么在例子中需要调用fflush才能看到内容。 现在需要将这些点组织成一个连贯的解释,并引用相关资料中的例子来说明。可能需要分点回答:文件描述符的作用、缓冲区的类型及触发条件、stdout与描述符的关系、重定向对缓冲的影响,以及例子分析。</think>### stdout缓冲与文件描述符的关系及作用机制 #### 1. **文件描述符的作用** 文件描述符是操作系统用于标识已打开文件的整数值。在Linux中,标准输入(stdin)、标准输出(stdout)、标准错误(stderr)分别对应描述符$0$、$1$、$2$[^1][^5]。 - **示例**:`printf()`默认向`stdout`输出,等价于`fprintf(stdout, ...)`,最终通过描述符$1$写入数据[^1]。 #### 2. **缓冲区的类型与触发条件** C标准库的I/O操作采用缓冲区机制,分为三种模式: - **无缓冲(SYNC_NOW)**:数据立即写入文件(如`stderr`)。 - **缓冲(SYNC_LINE)**:遇到换符`\n`或缓冲区满时刷新(如终端输出的`stdout`)。 - **全缓冲(SYNC_FULL)**:缓冲区满时刷新(如普通文件的输出)[^2][^3]。 **关键规则**: - 当`stdout`指向**终端**时,默认是**缓冲**,`printf("hello\n")`会因`\n`触发刷新。 - 若`stdout`重定向到**文件**,则变为**全缓冲**,需手动调用`fflush()`或缓冲区满才能写入[^3][^4]。 #### 3. **stdout与文件描述符的关联** `stdout`是`FILE`类型的结构体(如示例中的`FILE_`),包含: - `fileno`字段:记录底层文件描述符(默认$1$)。 - 缓冲区字段(`buffer`)及刷新策略(`flags`)[^2][^3]。 **作用流程**: 1. `printf("数据")`将数据写入`stdout`的缓冲区。 2. 若满足刷新条件(如缓冲遇到`\n`),调用`write(fileno, buffer, size)`,通过描述符$1$写入终端或文件[^3]。 #### 4. **重定向对缓冲机制的影响** 当文件描述符$1$被重定向时(如关闭后打开新文件),`stdout`的`fileno`仍为$1$,但底层目标已改变: ```c close(1); // 关闭原stdout int fd = open("log.txt", O_WRONLY); // 新文件的fd=1 printf("hello"); // 数据写入stdout缓冲区,但不会自动刷新到文件 fflush(stdout); // 强制刷新缓冲区到log.txt ``` 此时,由于`log.txt`是普通文件,`stdout`的缓冲模式变为**全缓冲**,需手动调用`fflush()`或程序正常退出才能写入[^4]。 #### 5. **总结关系图** $$ \text{stdout结构体} \xrightarrow{\text{fileno=1}} \text{文件描述符1} \xrightarrow{\text{指向终端/文件}} \text{数据最终位置} $$ - **缓冲模式**由目标设备类型决定,影响刷新时机。 - **描述符重定向**改变数据流向,但不自动修改缓冲策略,需注意手动刷新[^3][^4]。 --- 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值