一、printf 缓冲区机制
printf 函数不会直接将数据输出到屏幕,而是先将数据写入缓冲区。
满足以下任一条件时,缓冲区内容才会输出到屏幕:
(1)缓冲区被写满;
(2)强制刷新缓冲区(如 \n 换行符可触发缓冲区刷新);
(3)进程结束。
二、进程退出函数:exit () 与 _exit ()
1. 核心区别
| 特性 | exit() | _exit() |
|---|---|---|
| 函数类型 | C 标准库函数 | Unix 系统调用 |
| 标准 I/O 缓冲区处理 | 强制刷新并关闭(内容输出) | 不刷新,直接丢弃数据 |
| 用户态资源清理 | 执行(退出处理函数、资源清理) | 跳过,直接终止 |
2. exit () 执行流程
exit () 会先完成用户态资源清理,再触发内核级进程终止:
- 执行用户注册的 "退出处理函数";
- 刷新并关闭标准 I/O 流(缓冲区内容强制输出);
- 清理进程私有资源;
- 调用 _exit() 通知内核回收进程。
3. _exit () 执行逻辑
_exit() 跳过所有用户态操作,直接通知内核终止进程:
- 不执行退出处理函数;
- 不刷新标准 I/O 缓冲区;
- 内核直接回收进程资源。
4. 用法建议
- 普通单进程程序:优先使用 exit(),保证缓冲区数据输出、资源正常清理;
- 多进程程序(父 / 子进程):子进程优先用 _exit()(避免干扰父进程用户态资源),父进程使用 exit()。
三、fork 进程复制
fork() 是 Linux 系统调用,作用是复制当前进程,创建一个几乎完全相同的子进程
1.基础用法
头文件:<unistd.h>
函数原型:pid_t fork(void)
2. 返回值说明(整数类型)
| 执行场景 | 返回值 | 用途 |
|---|---|---|
| 父进程中 | 子进程的 PID(正整数) | 识别、管理子进程 |
| 子进程中 | 0 | 确定子进程身份 |
| fork 失败 | -1 | 失败原因存入全局变量 errno(如内存不足、进程数达上限) |
3. 关键特性
(1)fork 生成的子进程会继承父进程缓冲区中的所有内容(若父进程缓冲区未刷新,子进程会复制这份未输出的数据)。
(2)子进程会从 fork() 之后的代码继续执行,不会重新执行 fork() 之前的代码
例1:当有\n时打印输出几个A?
代码:

思想:
(1)初始状态
只有 1 个主进程 P0,进入 for 循环:
(2)第 1 次循环(i=0)P0 执行 fork() → 创建子进程 P1
此时有 2 个进程:P0、P1
两个进程都执行 printf("A\n") → 输出 2 个 A
两个进程都执行 i++,i 变为 1,进入下一次循环
(3)第 2 次循环(i=1)P0 执行 fork() → 创建子进程 P2
P1 执行 fork() → 创建子进程 P3
此时有 4 个进程:P0、P1、P2、P3
4 个进程都执行 printf("A\n") → 输出 4 个 A
4 个进程都执行 i++,i 变为 2,循环结束
(4)总输出统计第 1 次循环输出 2 个 A + 第 2 次循环输出 4 个 A = 6 个 A
进程树:

通用公式:
对于 for(int i=0; i<n; i++) { fork(); printf("A\n"); }:
总进程数:2^n
总打印 A 的数量:2^(n+1) - 2
n=1:2^2-2=2 个 A
n=2:2^3-2=6 个 A
n=3:2^4-2=14 个 A
n=4:2^5-2=30 个 A
例2:当无\n时打印输出几个A?
代码:

思想:
(1)初始状态:
只有主进程 P0
缓冲区:空
进入循环 i=0
(2)第 1 次循环(i=0)P0 执行 fork() → 生成子进程 P1
P0 缓冲区:[A](执行了 printf("A"))
P1 缓冲区:[A](完全复制了 P0 此时的缓冲区)
两个进程都执行完 printf("A"),进入 i++,现在 i=1
(3)第 2 次循环(i=1)P0 执行 fork() → 生成子进程 P2
P0 缓冲区:[A, A](又执行了一次 printf("A"))
P2 缓冲区:[A, A](复制了 P0 此时的缓冲区)
P1 执行 fork() → 生成子进程 P3
P1 缓冲区:[A, A](又执行了一次 printf("A"))
P3 缓冲区:[A, A](复制了 P1 此时的缓冲区)
四个进程(P0, P1, P2, P3)都执行完 printf("A"),循环结束
(4)进程结束,刷新缓冲区P0:缓冲区 [A, A] → 输出 2 个 A
P1:缓冲区 [A, A] → 输出 2 个 A
P2:缓冲区 [A, A] → 输出 2 个 A
P3:缓冲区 [A, A] → 输出 2 个 A
(5)总输出:2 + 2 + 2 + 2 = 8 个 A
进程树:

通用公式:
对于 for(int i=0; i<n; i++) { fork(); printf("A"); }:
总进程数:2^n
每个进程最终缓冲区里有 n 个 A
总输出:n * 2^n例:n=2 → 2 * 4 = 8;n=3 → 3 * 8 = 24
二者对比:
| 情况 | 打印语句 | 缓冲区行为 | 总 A 数量 |
|---|---|---|---|
| 带换行 | printf("A\n") |
每次 printf 后立即清空缓冲区 |
6 个 |
| 不带换行 | printf("A") |
fork 时缓冲区被完整复制,进程结束才统一输出 |
8 个 |