1. 令人迷惑的printf()

在C语言中,由于stdio.h中的缓冲机制,printf的输出通常会受到缓冲区的影响。

这种影响可能非常微妙,并常常令人疑惑,比如我们来看下面这段代码

#include <stdio.h>
int main(void) {
printf("Hello World");
while(1);
}

在命令行中编译运行,发现他只是一味循环,输出不见了?!!

但是如果我们修改一下代码,添加一个换行符:

printf("Hello World\n");

就可以看到Hello World被输出了?!

2. stdio的缓冲机制解析

根据标准I/O的缓冲方式,printf的输出主要有以下几种情况:

2.1. 行缓冲(Line Buffering)

  • 默认情况下,面向终端(标准输出/stdout是终端)的文件流使用行缓冲
  • 缓冲区在以下情况下刷新
    1. 输出了一个换行符 \n
    2. 缓冲区被填满
    3. 主动调用刷新函数(如 fflush(stdout))。
    4. 程序正常结束,流被关闭(如 exit()return 导致流关闭)。

示例

printf("Hello, ");    // 不会立即输出,因为没有换行
printf("World\n"); // 输出 "Hello, World",因为遇到换行符

2.2. 全缓冲(Full Buffering)

  • 默认情况下,面向文件的文件流(如写入文件的FILE*使用全缓冲
  • 缓冲区在以下情况下刷新
    1. 缓冲区被填满
    2. 主动调用刷新函数(如 fflush(file_stream))。
    3. 程序正常结束,流被关闭(如 fclose()exit())。

示例

FILE *fp = fopen("output.txt", "w");
fprintf(fp, "Buffered output"); // 不会立即写入文件
fflush(fp); // 主动刷新缓冲区,写入文件
fclose(fp); // 关闭文件时自动刷新缓冲区

2.3. 无缓冲(Unbuffered)

  • 默认情况下,标准错误流stderr无缓冲的(因为需要及时显示错误信息)。
  • 缓冲区在每次调用I/O操作时都会刷新,数据直接输出到目标设备。
  • 如果通过 setvbufsetbuf 将流设置为无缓冲,则每次调用printf都会立即输出。

示例

fprintf(stderr, "This is an error message\n"); // 立即输出,不受缓冲机制影响

设置无缓冲流

setvbuf(stdout, NULL, _IONBF, 0); // 将 stdout 设置为无缓冲
printf("Immediate output"); // 每次调用都会直接输出

2.4. 缓冲区溢出或关闭时刷新

  • 如果缓冲区被填满,stdio会自动刷新。
  • 当程序结束或流关闭时(如 fclose()),缓冲区中的内容会被自动刷新。

stdio缓冲机制总结

缓冲模式 使用场景 刷新条件
行缓冲 stdout面向终端 换行符、缓冲区满、调用fflush、流关闭或程序退出
全缓冲 stdout面向文件或其他设备 缓冲区满、调用fflush、流关闭或程序退出
无缓冲 stderr或主动设置无缓冲流 每次调用printffprintf直接输出

缓冲模式可以通过 setvbufsetbuf 自定义,这在调试或控制输出行为时非常有用。

3. 并发场景下的stdio缓冲

在并发场景下,stdio的缓冲机制可能会更令人迷惑一点,不过机制是相通的。

$ cat fork_printf.c
#include <stdio.h>
#include <unistd.h> int main(void) {
for (int i = 0; i < 2; i++) {
fork();
printf("Hello\n");
}
return 0;
}
$ gcc fork_printf.c
$ ./a.out
Hello
Hello
Hello
Hello
Hello
Hello
$ ./a.out | cat
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
$ # ??? 为什么两次输出内容不一样?是魔法么??!

这就是因为./a.out面向的输出的使用的缓冲方式不同:

  • 面向标准输出stdout时,使用行缓冲机制,Hello\n不存放在stdio的缓冲区(内存中),而是直接输出了
  • 面向管道输出时, 则使用全缓冲机制,因此第一个Hello\n会存放在缓冲区中,并随着fork一并复制,并再最后程序退出时输出。

也许你觉得我在胡说八道,但是根据计算机中没有魔法的观点,我们一定是有办法验证我们的猜想的。

没错,我们可以使用strace来看到程序的write系统调用,从而验证上述观点。

下一篇将以此为例介绍linux神器之strace的应用场景与使用方式。

stdio.h的缓冲机制解析的更多相关文章

  1. Printf的缓冲机制

    转:https://blog.csdn.net/qq_25424545/article/details/78772959 今天用fork()写程序时候,突然发现自己对Printf的缓冲机制还是有些不够 ...

  2. 走进C标准库(2)——"stdio.h"中的fopen函数

    其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...

  3. GO语言练习:channel 缓冲机制

    1.代码 2.运行 3.解析 1.代码 buffer.go package main import ( "fmt" "time" ) func readThre ...

  4. c语言输入与输出库函数#include<stdio.h>

    last modified: 2010-05-28 输入与输出<stdio.h> 头文件<stdio.h>定义了用于输入和输出的函数.类型和宏.最重要的类型是用于声明文件指针的 ...

  5. C语言中.h和.c文件解析(很精彩)

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析 ...

  6. C语言中.h和.c文件解析

    整理自C语言中.h和.c文件解析(很精彩) Part.1(林锐<高质量C/C++编程>) 通过头文件来调用库功能.在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的 ...

  7. 走进C标准库(5)——"stdio.h"中的其他部分函数

    函数介绍来自:http://ganquan.info/standard-c/ 函数名: freopen 功  能: 替换一个流 用  法: FILE *freopen(char *filename, ...

  8. 走进C标准库(3)——"stdio.h"中的getc和ungetc

    接前文. 再来看看getc和ungetc的实现.在看这两个函数的实现之前,我们先来想一想这两个函数分别需要做的工作. int getc(FILE *stream) 说明:函数getc从stream指向 ...

  9. java Swing 图片缓冲机制

    java Swing 图片缓冲机制: 参考:http://jorneyr.iteye.com/blog/868858#comments package util; import java.awt.ge ...

  10. 转-C语言中.h和.c文件解析

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:       1.预处理阶段 2.词 ...

随机推荐

  1. ARC143E Reversi

    ARC143E Reversi 简单的分析题. 思路 如果分析一个节点状态,那么时不方便的.但可以注意到,状态的改变好相连的边数有关. 从叶子节点开始考虑. 白色:在父亲翻转前选中,并改变父亲状态. ...

  2. PHP之常用第三方类库汇总

    汇总项目中经常使用到的第三方类库, 方便日后查找与使用 1.Oauth授权认证 https://github.com/jumbojett/OpenID-Connect-PHP 使用: [安装] com ...

  3. golang项目之Makefile

    01  介绍 Go 提供一个名为go的命令,该命令可自动下载.构建.安装和测试 Go 包和命令. Go 提供go命令,官方的目的是为了不需要编写 Makefile,而是能够仅使用 Go 源代码本身中的 ...

  4. 内网穿透之frp

    官网文档:https://gofrp.org 1 Frp介绍frp 是一个开源.简洁易用.高性能的内网穿透和反向代理软件,支持 tcp, udp, http, https等协议.frp 项目官网是 h ...

  5. 终端工具之Tabby

    我工作中用的最多的工具之一就是「终端连接工具」了,因为经常和 Linux 服务器打交道,经常在上面跑代码.排查系统问题等等. Windows 系统最常用的就是 Xshell 这款终端工具了,但是它只能 ...

  6. springboot 实现通用责任链模式

    1.概述 在我们平时的工作中,填写分布填写数据,比如填入商品的基本信息,所有人信息,明细信息,这种情况就可以使用责任链模式来处理. 2.代码实现 2.1商品对象 public class Produc ...

  7. 如何在 Epicor 中计算绩效

    制造性能是任何生产工序的关键,允许企业衡量和评估其效率和生产力水平. 我们将探讨如何在 Epicor 中计算制造性能.计算整体设备效率(OEE) 时性能指标的价值.如何解释制造指标以及在 Epicor ...

  8. OpenEuler文件被锁定的解决方法|网卡修改不生效的解决办法

    欧拉系统(含centos等linux系统)修改文件,一直提示readonly,不让改.原因有可能是这个文件给锁定了. 解决方法: 使用以下两个命令: • chattr 改变文件属性 • lsattr ...

  9. 12C++循环结构-for循环(2)

    一.循环变量为字符型 试编一程序,按字典顺序输出26个字母. 流程图: 程序代码如下: #include <iostream> //包含输入输出流头文件iostream using nam ...

  10. GooseFS 统一命名空间 | 加速存储业务访问

    01 前言 ​ GooseFS是 腾讯云存储团队推出的分布式缓存方案,主要针对需要缓存加速的数据湖业务场景,提供基于对象存储COS服务的近计算端数据加速层. 统一命名空间是GooseFS提供的透明命名 ...