IO缓冲区

一、标准IO的缓冲区

1.数据流动流程图

复制代码
程序
 ↓
FILE 指针
 ↓
FILE 结构体 ──→ struct _IO_FILE {
                    int _flags;        // 缓冲区类型标志(如全缓冲、行缓冲)
                    ...其他字段
                    char* _IO_buf_base;  // 缓冲区起始地址
                    char* _IO_buf_end;   // 缓冲区结束地址
                };
 ↓
缓冲区
 ↓ (满足刷新条件:缓冲区满/程序正常结束等等)
硬件

2.缓冲区的概念

定义:标准 I/O 中的缓冲区是C语言标准库为优化文件读写效率设计的内存机制,本质上就是一段由FILE 结构体管理的内存空间;

• 作用:通过临时存储数据、批量处理,可以减少硬件IO(如磁盘、终端)的交互次数,提升程序效率;

• 缓冲策略:标准 I/O 中设计了行缓冲、全缓冲、无缓冲三种策略,来适配不同的场景,也支持手动修改;

行缓冲:默认用于终端设备(如stdout、stdin),Linux系统中行缓冲的大小为1024(1KB);

全缓冲:默认用于文件(如 .txt),Linux系统中全缓冲大小通常是4096(4KB);

无缓冲:不使用临时内存暂存,数据直接写入硬件(如stderr);无缓冲区的大小为0;

二、缓冲区的刷新机制

1.行缓冲刷新时机

• 遇到换行符"\n"会刷新缓冲区

bash 复制代码
#include <stdio.h>
#include <unistd.h> // sleep()

int main() {
    printf("Hello, ");
    sleep(3);  // 等3秒,你会发现这句话没立刻打印出来
    printf("World!\n"); // 遇到\n,缓冲区被刷新,内容一起输出
    sleep(3);
    return 0;
}

• 程序正常结束会刷新缓冲区

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("程序结束前,这句话会被刷新出来");
    sleep(5); // 睡5秒,屏幕上不会立刻看到这句话
    return 0;  // main函数return,程序正常结束,缓冲区被强制刷新
}

• 当文件关闭时会刷新缓冲区

cs 复制代码
#include <stdio.h>

int main() {
    FILE* fp = fopen("test.txt", "w");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    fputs("这行数据先写到缓冲区,还没真正进文件", fp);
    // 此时如果用编辑器打开 test.txt,内容是空的

    fclose(fp); // 文件关闭,缓冲区被刷新,数据写入磁盘
    // 现在再打开 test.txt,就能看到内容了

    return 0;
}

• 在输入和输出发生切换时会刷新缓冲区

cs 复制代码
#include <stdio.h>

int main() {
    printf("请输入一个数字:"); // 先输出,没加\n
    int n;
    scanf("%d", &n); // 切换到输入时,会自动刷新输出缓冲区
    printf("你输入的数字是:%d\n", n);
    return 0;
}

• 缓冲区写满(未到换行符)会刷新缓冲区

cs 复制代码
#include <stdio.h>

int main() {
    // 对文件流默认是全缓冲,缓冲区一般是4KB或8KB
    FILE* fp = fopen("big_test.txt", "w");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    // 写一个很大的字符串,超过缓冲区大小
    char big_buf[1024*8];
    for (int i = 0; i < sizeof(big_buf); i++) {
        big_buf[i] = 'A';
    }
    big_buf[sizeof(big_buf)-1] = '\0';

    fputs(big_buf, fp);
    // 此时缓冲区已满,会自动刷新到文件,不用等fclose

    getchar(); // 暂停程序,此时打开文件已经能看到内容了
    fclose(fp);
    return 0;
}

• 主动调用fflush函数会强制刷新缓冲区

项目 内容说明
所需头文件 #include <stdio.h>
函数原型 int fflush(FILE *stream);
功能 将指定流 (stream) 缓冲区中尚未写入的内容,强制写入到对应的输出设备(如文件、终端等)中
参数 stream:用于指定需要刷新缓冲区的流
返回值 - 成功 返回 0,表示成功刷新了流缓冲区
返回值 - 失败 返回 EOF,并将 errno 设置为具体的错误类型
cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("我要立刻被刷新!");
    fflush(stdout); // 强制刷新标准输出缓冲区
    sleep(3);       // 此时你会立刻看到文字,然后等3秒
    printf(" 结束啦\n");
    return 0;
}

2.全缓冲刷新机制

• 程序正常结束会刷新缓冲区

cs 复制代码
#include <stdio.h>

int main() {
    FILE* fp = fopen("full_buffer_test.txt", "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 写入数据,此时数据只在缓冲区,没写入文件
    fputs("这行数据在缓冲区,未写入磁盘\n", fp);
    printf("数据已写入缓冲区,但文件还没真正写入\n");

    // 暂停,你可以此时打开文件查看,内容是空的
    getchar();

    // 调用fclose,会自动刷新缓冲区,数据写入文件
    fclose(fp);
    printf("文件已关闭,缓冲区数据已刷新到磁盘\n");
    return 0;
}

• 当文件关闭时会刷新缓冲区

上同

• 在输入和输出发生切换时会刷新缓冲区

cs 复制代码
#include <stdio.h>

int main() {
    FILE* fp = fopen("io_switch_test.txt", "w+");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    fputs("先写入缓冲区的数据", fp);
    // 此时数据在缓冲区,未写入文件

    // 切换到输入操作,会触发缓冲区刷新
    rewind(fp);  // 移动文件指针到开头
    char buf[100];
    fgets(buf, sizeof(buf), fp);  // 输入操作,强制刷新之前的输出缓冲区

    printf("从文件读取到的内容:%s\n", buf);
    fclose(fp);
    return 0;
}

• 缓冲区写满(未到换行符)会刷新缓冲区

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    FILE* fp = fopen("full_write_test.txt", "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 构造一个超过默认缓冲区大小的数据(比如8KB)
    char big_data[1024 * 8];
    memset(big_data, 'A', sizeof(big_data) - 1);
    big_data[sizeof(big_data) - 1] = '\0';

    printf("开始写入大数据,超过缓冲区大小会自动刷新\n");
    fputs(big_data, fp);

    // 此时数据已经因为缓冲区写满被刷新到文件了
    getchar();  // 暂停,此时打开文件能看到内容

    fclose(fp);
    return 0;
}

• 主动调用fflush函数会强制刷新缓冲区;

cs 复制代码
#include <stdio.h>

int main() {
    FILE* fp = fopen("fflush_test.txt", "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    fputs("还在缓冲区的数据", fp);
    printf("数据在缓冲区,未写入文件\n");

    // 主动调用fflush强制刷新
    fflush(fp);
    printf("fflush调用后,数据已写入文件\n");

    getchar(); // 暂停,此时打开文件能看到内容
    fclose(fp);
    return 0;
}

三、手动设置缓冲区

1.为什么需要手动设置缓冲?

• 覆盖默认缓冲策略:不同系统/语言对IO流的默认缓冲设置不同(例如C语言中stdout默认行缓 冲,stderr默认无缓冲),手动设置可按需调整;

• 性能与实时性平衡:

• 行缓冲:适合交互式输出(如命令行程序),每行输出后自动刷新;

• 全缓冲:适合大量数据批量写入(如日志文件),减少IO次数;

• 无缓冲:适合需要立即显示的关键信息(如错误日志);

标准 I/O(C 标准库提供的 stdio.h 系列函数,如 printf/fread/fwrite)通过用户态缓冲区,减少直接调用系统调用(如 read/write)的次数。

优点:

1.大幅减少系统调用次数,提升性能

系统调用会陷入内核态,开销较大。缓冲机制将多次小 I/O 合并为一次批量操作,减少上下文切换次数,显著提升效率。

2.适配不同设备的访问特性

例如磁盘设备适合批量读写,终端设备适合按行交互,缓冲模式可以根据设备特性优化行为。

3.简化应用层逻辑

开发者无需手动管理 I/O 批次,标准库自动处理缓冲刷新,降低编程复杂度。

缺点:

1.数据不是实时写入设备,存在延迟风险

数据暂存在用户态缓冲区,若程序意外崩溃(如 SIGSEGV),缓冲区数据未刷新,会导致数据丢失。

2.调试和异常处理复杂度提升

例如 printf 未加 \n 时,程序崩溃前不会输出内容,给调试带来干扰;需额外调用 fflush 或设置无缓冲模式。

占用额外内存空间

3.每个流都需要维护缓冲区,增加了进程的内存开销。

可能造成数据不一致

若多个进程 / 线程同时操作同一文件,缓冲数据未刷新时,会出现视图不一致的问题。

• 特殊场景需求:例如网络编程中避免数据堆积,或调试时强制输出中间结果; • 手动设置缓冲典型应用场景:

• 日志系统:根据日志级别设置缓冲(错误日志无缓冲,普通日志全缓冲);

• 交互式工具:命令行程序中确保用户输入后立即响应;

• 嵌入式系统:受限内存环境下自定义缓冲区大小;

2.setbuf函数

项目 内容说明
所需头文件 #include <stdio.h>
函数原型 void setbuf(FILE *stream, char *buf);
功能 设置指定文件流的缓冲区
参数 -stream 指向FILE对象的指针,标识要设置缓冲区的文件流
参数 -buf 指向用户提供的缓冲区的指针;若提供缓冲区,其大小必须至少为BUFSIZ字节;若为NULL,则设置为无缓冲模式
返回值
  1. 为文件流设置自定义缓冲区
cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    // 定义一个大小为 BUFSIZ 的缓冲区(stdio.h 中定义的默认缓冲区大小)
    char my_buf[BUFSIZ];

    FILE* fp = fopen("setbuf_test.txt", "w");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    // 为文件流 fp 设置自定义缓冲区
    setbuf(fp, my_buf);

    // 写入数据,此时数据会先写入我们定义的 my_buf 中
    fputs("这行数据会先进入我们自定义的缓冲区\n", fp);
    printf("数据已写入自定义缓冲区,未写入文件\n");

    // 暂停 3 秒,你可以此时打开文件查看,内容是空的
    sleep(3);

    // 关闭文件,会自动刷新缓冲区,数据写入文件
    fclose(fp);
    printf("文件已关闭,缓冲区数据已刷新到磁盘\n");

    return 0;
}
  1. 设置无缓冲模式(数据直接写入设备)
cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    // 为标准输出设置无缓冲模式,数据直接输出到终端
    setbuf(stdout, NULL);

    printf("无缓冲模式:");
    sleep(3); // 即使没有 \n,也会立刻打印文字,不会等待缓冲区刷新
    printf("数据直接输出,没有缓冲延迟!\n");

    return 0;
}

3.结合 setbuf 与 fflush 强制刷新

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    char my_buf[BUFSIZ];
    FILE* fp = fopen("setbuf_fflush.txt", "w");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    setbuf(fp, my_buf);
    fputs("数据在自定义缓冲区,未写入文件", fp);
    printf("数据在缓冲区,等待刷新...\n");

    sleep(3);
    // 主动调用 fflush 强制刷新自定义缓冲区
    fflush(fp);
    printf("已调用 fflush,数据写入文件\n");

    fclose(fp);
    return 0;
}

💡 关键说明:

setbuf 必须在流打开后、任何读写操作前调用,否则行为未定义。

buf 参数必须是全局变量或静态变量,不能是局部栈变量,否则函数结束时缓冲区被释放会导致崩溃。

setbuf(fp, NULL) 等价于关闭该流的所有缓冲,每次写入都会直接刷新到设备。

• setbuf函数允许两种方式的缓冲:

• 全缓冲:如果buf指向一个大小大于0的缓冲区,那么IO操作将使用全缓冲模式;

• 无缓冲:如果buf为NULL,或者是大小为0,那么IO操作将变为无缓冲模式;

• setbuf函数注意事项:

• 调用setbuf函数应该在打开文件流或者是任何IO操作之前,否则可能不会生效;

• 如果要使用自定义的缓冲区,确保缓冲区足够大,以避免缓冲区溢出;

• 当使用自定义缓冲区时,不要在buf指向的缓冲区被释放后再进行IO操作,以免导致未定义的行为;

2.sleep函数

项目 内容说明
所需头文件 #include <unistd.h>
函数原型 unsigned int sleep(unsigned int seconds);
功能 用于让程序暂停执行指定的时间
参数 seconds:指定程序暂停执行的秒数
返回值 - 成功 返回 0
返回值 - 失败 若程序被信号中断,返回未休眠完成的剩余秒数

1. 基础用法:程序暂停指定秒数

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("程序开始执行...\n");
    
    // 让程序暂停 3 秒
    printf("暂停 3 秒...\n");
    sleep(3);
    
    printf("3 秒已过,程序继续执行!\n");
    return 0;
}

运行效果:先打印前两行,等待 3 秒后再打印最后一行。

2. 处理信号中断的情况

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// 信号处理函数:当收到 SIGINT (Ctrl+C) 时触发
void sig_handler(int sig) {
    printf("\n收到信号 %d,程序被中断!\n", sig);
}

int main() {
    unsigned int remain;
    signal(SIGINT, sig_handler); // 注册信号处理函数

    printf("程序将暂停 10 秒,按 Ctrl+C 可中断\n");
    remain = sleep(10);

    if (remain > 0) {
        printf("sleep 被中断,剩余未休眠的秒数:%u\n", remain);
    } else {
        printf("sleep 完整执行完毕\n");
    }

    return 0;
}

运行效果:

不中断:等待 10 秒后,打印 sleep 完整执行完毕

中途按 Ctrl+C:程序会立即打印剩余秒数(比如休眠了 3 秒被打断,返回值是 7)

3. 配合 printf 缓冲区的使用(经典场景)

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("加载中");
    // 不刷新缓冲区,先休眠3秒
    sleep(3);
    printf("...完成!\n"); // 遇到换行符,才会把前面的内容一起输出
    return 0;
}

运行效果:先等待 3 秒,再一次性打印 加载中...完成!。

如果想让文字实时显示,需要主动刷新缓冲区:

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("加载中");
    fflush(stdout); // 强制刷新标准输出缓冲区
    sleep(3);
    printf("...完成!\n");
    return 0;
}

此时会立刻显示 加载中,等待 3 秒后再补充 ...完成!。

💡 补充说明:

sleep 是秒级休眠,精度较低。如果需要毫秒级 / 微秒级控制,可以使用 usleep()(微秒)或 nanosleep()(纳秒)。

sleep 会让进程进入阻塞状态,不占用 CPU 资源,适合做延时等待场景。

3.setvbuf函数

项目 内容说明
所需头文件 #include <stdio.h>
函数原型 int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能 灵活控制文件流的缓冲行为,可指定缓冲模式和缓冲区大小;必须在流执行任何读写操作前调用
参数 -stream 指向FILE对象的指针,标识要设置缓冲区的文件流
参数 -buf 用户提供的缓冲区指针;若为NULL,函数会自动分配一个大小为size的缓冲区
参数 -mode 缓冲模式:- _IOFBF:全缓冲- _IOLBF:行缓冲- _IONBF:无缓冲(此时size参数被忽略)
参数 -size 缓冲区的大小(字节)
返回值 - 成功 返回 0
返回值 - 失败 返回非 0 值

1. 为文件流设置自定义全缓冲

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    // 自定义一个 1024 字节的缓冲区
    char my_buf[1024];
    FILE* fp = fopen("setvbuf_full.txt", "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 设置为全缓冲模式,使用自定义缓冲区
    if (setvbuf(fp, my_buf, _IOFBF, sizeof(my_buf)) != 0) {
        perror("setvbuf");
        fclose(fp);
        return 1;
    }

    fputs("数据写入自定义全缓冲中,未写入文件\n", fp);
    printf("数据在缓冲区,等待刷新...\n");

    sleep(3); // 暂停3秒,此时文件内容为空
    fclose(fp); // 关闭文件,缓冲区刷新到文件
    printf("文件已关闭,数据写入完成\n");
    return 0;
}

2. 为标准输出设置行缓冲

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    // 为 stdout 设置行缓冲模式,缓冲区大小 1024 字节
    char line_buf[1024];
    if (setvbuf(stdout, line_buf, _IOLBF, sizeof(line_buf)) != 0) {
        perror("setvbuf");
        return 1;
    }

    printf("这行文字遇到换行符");
    sleep(3);
    printf("才会被刷新出来\n"); // 遇到\n,行缓冲刷新
    sleep(3);
    return 0;
}

3. 设置无缓冲模式(数据直接输出)

cs 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    // 为 stdout 设置无缓冲模式,size 会被忽略
    if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
        perror("setvbuf");
        return 1;
    }

    printf("无缓冲模式:");
    sleep(3); // 即使没有换行,也会立刻打印文字
    printf("文字直接输出,无缓冲延迟!\n");
    return 0;
}

💡 关键注意事项:

调用时机:必须在fopen之后、任何printf/fputs等读写操作之前调用setvbuf,否则行为未定义。

缓冲区生命周期:如果使用自定义缓冲区,它的生命周期必须长于文件流的生命周期,不能是局部栈变量(否则函数结束时缓冲区被释放,会导致程序崩溃)。

无缓冲模式:设置_IONBF时,size参数会被忽略,数据每次都会直接写入设备。

4.setbuf 与 setvbuf 的异同点对比

两者都是 C 标准库中用于设置文件流缓冲区的函数,核心目标都是控制流的缓冲行为,但在功能灵活性、参数设计上有明显差异。

✅ 相同点

核心作用一致:都用于修改文件流(FILE*)的缓冲模式,包括设置自定义缓冲区、关闭缓冲、修改缓冲大小。

调用时机要求相同:必须在流打开后、任何读写操作(如printf/fread)之前调用,否则行为未定义。

头文件依赖一致:都需要包含 <stdio.h>。

都支持无缓冲模式:都可以将流设置为无缓冲模式,数据直接写入设备。

❌ 不同点

对比维度 setbuf setvbuf
函数原型 void setbuf(FILE *stream, char *buf); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能灵活性 仅支持两种模式:1. 全缓冲(传入buf,大小固定为BUFSIZ)2. 无缓冲(传入NULL 支持完整的缓冲控制:1. 可指定缓冲模式(全缓冲 / 行缓冲 / 无缓冲)2. 可自定义缓冲区大小3. 可让系统自动分配缓冲区
缓冲模式选择 无法指定行缓冲模式,只能用默认模式 可通过mode参数选择:_IOFBF:全缓冲_IOLBF:行缓冲_IONBF:无缓冲
缓冲区大小 必须使用BUFSIZ(stdio.h 定义的固定大小) 可通过size参数自定义任意大小
返回值 无返回值,无法直接判断调用是否成功 返回0表示成功,非 0 表示失败,可判断错误
可移植性 是 C 标准的一部分,但功能有限 也是 C 标准的一部分,是setbuf的增强版,更推荐使用
相关推荐
wheeldown2 小时前
2026年4月横评三款主流远控软件实况实测:UU远程,Todesk,向日葵,综合性能 UU 远程表现最佳
linux·运维·服务器
诗句藏于尽头2 小时前
CentOS 7 源码编译安装 Python 3.11 完整教程
linux·centos·python3.11
pixcarp2 小时前
Nginx实战部署与踩坑总结 附带详细配置教程
服务器·前端·后端·nginx·golang
bksczm2 小时前
Linux之基础开发工具(Ubuntu)之apt 、vim
linux·ubuntu·php
va学弟2 小时前
Agent入门开发(2):个性化功能添加
java·服务器·ai
java_logo2 小时前
Docker 部署 Open WebUI + Ollama 完整教程(Windows / Linux 通用)—— 打造自己的本地OpenAI
linux·docker·容器·ollama·open-webui·open-webui部署·open-webui教程
IP老炮不瞎唠2 小时前
什么是Grok?以及如何解决卡顿、风控问题
服务器·网络
淼淼爱喝水2 小时前
eNSP 防火墙 NAT 策略配置(Easy IP/No-PAT/NAPT/ 黑洞路由)
服务器·网络·tcp/ip·ensp·防火墙·nat
小夏子_riotous2 小时前
Docker学习路径——8、Dockerfile
linux·运维·docker·容器·系统架构·centos·运维开发