一、进程退出两大类
1. 正常退出(主动自杀,进程主动结束自身)
进程主动执行退出逻辑,自行结束生命周期,主要分为三种方式:main函数return、标准库exit()、系统调用_exit()。
1)main 函数 return
main 的
return n等价于调用exit(n)。普通函数里的 return 只是函数返回,不会结束进程。
只有 main 函数的 return 会触发完整进程退出流程:结束进程、刷新 IO 缓冲区、回收进程资源。
2)库函数 exit() 标准库退出
头文件:#include <stdlib.h>
void exit(int status);status:进程退出码,标识退出状态。0 代表正常退出,非 0 代表异常退出,可由父进程
wait()获取。执行流程(带完整用户层清理):
刷新标准 IO 缓冲区,将 printf、文件缓存数据落盘或输出到终端;
调用
atexit()注册的退出钩子函数;最终调用底层系统调用
_exit(),由内核真正终止进程。
3)系统调用 _exit() 直接内核终止
头文件:#include <unistd.h>
void _exit(int status);直接进入内核销毁进程,完全跳过用户层清理工作;
不刷新 IO 缓冲区、不执行 atexit 回调;
缓冲区未落地数据直接丢失,会出现 printf 无打印现象。
关键示例对比
示例 1:_exit 丢失缓存,无打印
#include <stdio.h> #include <unistd.h> int main() { printf("hello world"); // 数据存在缓冲区,未换行不刷新 _exit(0); return 0; }运行结果:终端无任何输出
示例 2:exit 自动刷新缓存,正常打印
#include <stdio.h> #include <stdlib.h> int main() { printf("hello world"); exit(0); return 0; }运行结果:终端输出 hello world
补充小知识点
如果 printf 末尾加换行\n,标准行缓冲会强制刷新,此时 _exit 也能正常打印内容:
printf("hello world\n"); _exit(0); // 能正常输出,换行触发缓冲区刷新2. 非正常退出(被动他杀,外部 / 异常强制终止)
进程没有主动调用任何退出函数,由程序异常或外部信号干涉,被强制杀死。分为两类场景:
1)程序运行异常崩溃
段错误 SIGSEGV:访问非法内存、空指针、数组越界
总线错误 SIGBUS:非法地址对齐访问、访问无效硬件地址
浮点错误 SIGFPE:除数为 0、浮点运算溢出
2)外部发送终止信号
kill 进程PID:发送 SIGTERM 温柔杀死,进程可捕获信号、执行收尾工作kill -9 进程PID:发送 SIGKILL 强制杀死,进程无法捕获、无法清理,内核直接销毁进程
二、exit / _exit / main return 核心差异表
方式 | 刷新 IO 缓冲区 | 执行 atexit 钩子 | 层级 |
|---|---|---|---|
main 函数 return | 是 | 执行 | 用户层封装 |
exit() | 是 | 执行 | 标准库函数 |
_exit() | 否 | 不执行 | 系统调用 |
三、拓展补充考点
退出码取值规则:退出码仅低 8 位有效,有效范围 0~255,父进程通过
wait/waitpid获取退出状态。缓冲区本质:printf 是标准库封装,数据存放在用户态缓冲区;
_exit直接进入内核销毁进程,丢弃用户态缓存;exit在调用内核接口前主动刷新缓冲区。fork 子进程场景:子进程结束推荐使用
_exit,避免刷新父子共享的缓冲区,造成数据重复输出、日志错乱问题。