stdio.h的缓冲机制解析

news/2025/1/26 13:37:29/文章来源:https://www.cnblogs.com/hk416hasu/p/18691722

1. 令人迷惑的printf()

在C语言中,由于stdio.h中的缓冲机制,printf的输出通常会受到缓冲区的影响。
这种影响可能非常微妙,并常常令人疑惑,比如我们来看下面这段代码

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

在命令行中编译运行,发现他只是一味循环,输出不见了?!!
但是如果我们修改一下代码,添加一个换行符:

printf("Hello World\n");

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

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的应用场景与使用方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/875949.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【新能源行业】新能源汽车电子驻车制动系统(EPB)谁在做?

长期以来,汽车的动力系统一直是人们所关注的焦点,然而,汽车制动系统在背后默默支撑起整个汽车安全与稳定。其重要性丝毫不亚于动力系统。行车上路,安全第一。在每一次的启程与停驻之间,唯有制动系统作为坚实保障,才能让每一次出行都安心无虞。一、制动系统分类与组成 目前…

如何从内存中提取shellcode

恶意程序有时会直接在内存中运行shellcode 。在这篇文章中,我将向你展示如何从内存中获取shellcode。 shellcode在内存中的位置 在内存中分配shellcode的常用方法是使用VirtualAlloc来分配具有所需权~限的内存。然后恶意软件使用RtlMoveMemory将shellcode写入分配的空间。然后…

施耐德UNITY中使用ST 语言计算日均值

以前做过练习,在unity中计算分钟均值和小时均值,做成自定义功能块。今天在家打算按照同样的思路,试着做一下日均值。 第一次打算建立一个三维数组PV_DAY[0..23,0..59,0..59],每秒存放一个数据,编译的时候提示数组太大。 第二次尝试建立24个数组,每个数组存放一个小时内36…

【转载】rpm 和 yum 软件包的应用

本节所讲内容:8.1 使用rpm命令-安装-查看-卸载-rpm软件包8.2 yum管理软件包8.3 CentOS8中使用DNF管理软件包8.4 实战tar源码包管理-源码包安装方法8.1 软件包的管理软件包的类型rpm二进制包------》已经使用GCC编译后的(二进制已经可以被操作系统直接执行了)tar源码包-----》…

[Redis] Redis (5) 多核多线程架构

序 引言Redis 作为一款高性能的内存数据库,以其简单的设计和单线程模型(潜台词:单核单线程)广受欢迎。 然而,随着用户需求和数据规模的增长,单线程的架构逐渐成为 Redis 性能的瓶颈。 近年来,Redis 开始引入部分多线程机制,以提高并发性能,特别是在处理网络 I/O 和数据持…

Python并行计算与高性能计算7迎接并行计算革命

在本章中,我们将介绍我们在前几章中看到的并行编程的实际方面。随着并行计算概念的扩展,它不仅包括并行编程及其相关方面,还包括能够管理并专门设计的基础设施。超级计算机通常被定义为由许多 CPU 和 GPU 组成的高性能系统,其中应用了并行计算和高性能计算 (HPC) 方法。本章…

人脸识别和神经风格转换

人脸识别和神经风格转换 人脸识别人脸验证(Verification):验证输入图像是否属于某个特定身份,属于一对一问题。 人脸识别(Recognition):一对多问题,从大量数据中找到匹配的人脸。 在很多人脸识别应用中,系统需要通过单一样本识别某人,而非多个样本,这就属于 One-shot Le…

dubbo 2.7.2 启动报错【Unsupported generic type false】排查

💖1.问题现象 dubbo服务启动时抛出异常Unsupported generic type false,但不影响服务正常发布。 Caused by: java.lang.IllegalArgumentException: Unsupported generic type false📖2. 版本信息 SpringBoot 2.1.3 + Dubbo 2.7.2 👉3. 问题根因 项目中使用了Spring Boot…

[Redis] Redis (5) 多核多线程模型

序 引言Redis 作为一款高性能的内存数据库,以其简单的设计和单线程模型(潜台词:单核单线程)广受欢迎。 然而,随着用户需求和数据规模的增长,单线程的架构逐渐成为 Redis 性能的瓶颈。 近年来,Redis 开始引入部分多线程机制,以提高并发性能,特别是在处理网络 I/O 和数据持…

案例分享-依赖传递引发的健康检查失败问题

背景 网关服务已成功发布,然而新创建的Pod却始终未能成功启动。在Pod的事件(Event)中,明确显示健康检查失败。但令人困惑的是,仔细查看启动日志,却未发现任何异常情况,具体情况如下图所示。 排查 既然当前问题表现为健康检查失败,那就有必要深入排查究竟是哪些关键部…

微信小程序逆向 ... 未完待续

解包 打开这个文件夹下图中,wxid_21arhynucfka22表示了不同微信号的id。打开的小程序缓存会被放入Applet文件夹中,比如图中wx31a9c726536cdacc 就是我之前打开过的 微信小程序下载的缓存。同时,在wechat目录下也存在所有账号公用的小程序缓存,公用的Applet文件夹,我们可以…

SourceMap的简单理解

什么是 SourceMap? Source Map 是一种映射文件,它可以将压缩、混淆后的代码还原回其原始的源代码。 (这种映射关系,可以准确地将编译后的代码映射回源代码。[自己理解就行]) 从而方便开发人员进行调试、错误日志收集和性能优化等工作。 它是一个以 .map 为后缀的文件。 为什么…