c 中 stdout, stderr 容易忽视的一些细节

本文详细解析了在C语言中使用文件流与文件描述符时遇到的问题,特别是关于标准输入输出流缓冲区的区别及如何正确处理以避免意外结果。通过实例演示了如何在不同情况下正确使用`fprintf`、`fflush`和`write`函数,确保输出顺序符合预期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看下面一个例子

a.c :

int main(int argc, char *argv[])
{
	fprintf(stdout, "normal\n");
	fprintf(stderr, "bad\n");
	return 0;
}

$ ./a
normal
bad

$ ./a > tmp 2>&1
$ cat tmp
bad
tmp

我们看到, 重定向到一个文件后, bad 到了 normal 的前面.

原因如下:
"The stream stderr is unbuffered. The stream stdout is line-buffered when it points to a
     terminal. Partial lines will not appear until fflush(3) or exit(3) is called, or a newline
     is printed. This can produce unexpected results, especially with debugging output.  The
     buffering mode of the standard streams (or any other stream) can be changed using the
     setbuf(3) or setvbuf(3) call. "

因此, 可以使用如下的代码:

int main(int argc, char *argv[])
{
	fprintf(stdout, " normal\n");
	fflush(stdout);
	fprintf(stderr, " bad\n");
	return 0;
}
这样重定向到一个文件后就正常了. 但是这种方法只适用于少量的输出, 全局的设置方法还需要用 setbuf() 或 setvbuf(), 或者采用下面的系统调用:

int main(int argc, char *argv[])
{
	write(1, "normal\n", strlen("normal\n"));
	write(2, "bad\n", strlen("bad\n"));
	return 0;
}

但是尽量不要同时使用 文件流 和 文件描述符, 

"Note that mixing use of FILEs and raw file descriptors can produce unexpected results and
     should generally be avoided.  A general rule is that file
     descriptors are handled in the kernel, while stdio is just a library. This means for exam-
     ple, that after an exec(), the child inherits all open file descriptors, but all old
     streams have become inaccessible."



### C语言中 `fprintf`、`printf`、`stdin`、`stdout` 和 `stderr` 的用法及区别 #### 函数与宏定义概述 在C语言编程环境中,`stdin`, `stdout`, `stderr` 是预定义的标准I/O流文件指针。这些文件指针对应着程序运行时默认连接至终端设备的三个标准通道:标准输入(通常是键盘)、标准输出(通常是指向屏幕)和标准错误输出(同样指向屏幕但专用于显示错误信息)。当操作系统启动一个进程时会自动创建这三个文件描述符并赋予特定编号——0对应`stdin`;1对应`stdout`; 2则分配给`stderr`。 对于输出操作而言: - **`printf()`** 函数可以看作是对 `stdout` 进行写入的一种便捷方式[^1]。其功能上等价于调用带有第一个参数设定为 `stdout` 的 `fprintf()` 函数。 - **`fprintf()`** 则是一个更为通用的形式化输出函数,允许指定任意有效的FILE * 类型的目标作为输出目的地,并支持格式化的字符串打印[^3]。 而对于错误消息处理方面有专门设计用来向 `stderr` 发送数据的方法: - 使用 perror() 可以方便地将当前线程最后一次发生的系统级错误转换成人类可读的信息并通过 `stderr` 显示出来,这实际上也是通过内部调用了类似于 `fprintf(stderr,"...")` 的机制实现。 关于缓冲特性上的差异值得注意的是: - 默认情况下,`stdout` 流采用行缓存模式工作,在接收到换行字符(`'\n'`)之前不会立即将待发送的数据刷新到实际目标位置除非显式执行了 `fflush(stdout)` 或者遇到了程序结束等情况触发强制刷盘动作; - 相较之下,为了确保异常情况能够被及时发现,`stderr` 设计成了无缓存状态,任何尝试往里面写入的内容都会立刻呈现给用户查看而不必等待额外条件满足后再做统一提交[^2]。 下面给出一段简单的示范代码来展示上述概念的实际应用效果: ```c #include <stdio.h> int main(void){ char str[]="Hello World!"; // 向标准输出打印一条带格式的消息 printf("%s\n",str); // 将相同内容再次发往 stdout ,形式略有不同而已 fprintf(stdout,"%s (via stdout)\n",str); // 假设发生某种错误状况,则可通过如下方法记录下来 fprintf(stderr,"Error occurred while processing %s (via stderr).\n",str); return 0; } ``` 此段代码先利用 `printf()` 打印了一条问候语句,接着又借助 `fprintf()` 对同一句话进行了重复输出只不过这次明确指定了流向为 `stdout` 。最后模拟了一个可能出现的错误场景并将相应提示经由 `stderr` 渠道传达出去以便引起注意。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值