一、进程退出场景
在编写 C/C++ 程序时,我们经常看到 return 0、exit(1) 甚至是程序崩溃后的“段错误”提示。但你是否真正理解这些退出场景背后的原理和差异?本文将从三类常见退出方式出发,深入解析进程退出的各种情况,以及退出码的含义和使用方式,帮助你写出更稳定、可预期的程序。
1、正常退出:一切顺利地结束
当程序的主逻辑顺利执行完毕,没有出现任何错误或异常时,通常以如下方式退出:
return 0;
exit(0);
两者本质上等效,表示程序“成功执行”,返回码为 0。这类退出方式称为“正常退出”。
代码示例:
#include
int main() {
std::cout << "Everything is OK." << std::endl;
return 0; // 或 exit(0);
}
2、非正常但可控的退出:程序逻辑失败
有时候程序虽然没有崩溃,但由于配置缺失、验证失败等原因,主动终止并返回非零退出码,如 1 或 -1。
示例:
#include
#include
int main() {
std::ifstream file("important_config.txt");
if (!file.is_open()) {
std::cerr << "Error: Could not open config file." << std::endl;
return 1; // 用非0退出码告知失败
}
std::cout << "Config loaded." << std::endl;
return 0;
}
3、异常终止:程序崩溃或未处理的异常
当程序中发生未处理的异常、运行时错误或接收到致命信号(如段错误 SIGSEGV)时,进程会被强制终止,称为“异常退出”。
示例:
int main() {
int* p = nullptr;
*p = 10; // 空指针解引用,SIGSEGV
}
4、总结
退出场景退出方式退出码特征正常运行完成return 0 / exit(0)0程序逻辑无误,正常结束逻辑失败return 1 / exit(1)非0主动退出,非崩溃异常终止(崩溃)段错误 / 未处理异常 / SIGSEGV非0程序被信号强制终止二、如何查看进程的退出码?
1、查看上一个程序运行的退出码
在 Linux/macOS 终端中,可以使用以下命令查看最近执行的命令的退出码:
echo $?
这会显示上一个命令的返回值。
比如我正常运行process_bar程序之后,输入echo $?,则返回我运行该程序的返回值(process_bar程序是正常运行的)。
2、有哪些退出码呢
我们可以尝试遍历所有的错误码。在这里就要提到两个函数,errno和strerror。
errno 是一个全局变量,定义在
strerror 是一个函数,用于根据给定的错误码返回一个描述该错误的字符串。
#include
#include
#include
int main() {
for (int i = 1; i < 134; ++i) {
errno = i;
std::cout << i << ": " << strerror(errno) << std::endl;
}
return 0;
}
在如下的代码中,遍历了标准的错误码范围(通常是从 1 到 133,具体范围可以参考你的系统)。你可以根据需要调整范围,或者使用系统中定义的错误码数量。
三、exit和_exit的区别
在 C 和 C++ 中,exit 和 exit_ 有很大的不同。事实上,exit_ 并不是一个标准的函数,而 exit 是标准库函数之一。以下是这两个名称的详细对比和解释:
1、exit
exit 是 C 和 C++ 标准库中定义的一个函数,用于终止程序的执行,并且可以返回一个状态码给操作系统。它定义在
void exit(int status);
status:这是程序的退出状态码。通常,0 表示程序成功执行,非 0 表示程序出现错误或异常。
功能:
exit 会执行以下操作:
执行所有已注册的退出函数(通过 atexit() 注册的)。刷新所有输出流,以确保缓冲区中的数据写入文件或终端。关闭所有打开的文件描述符。最终终止程序并返回控制权给操作系统。
代码示例:
#include
#include
int main() {
// 使用 std::endl 让输出缓冲区被刷新
std::cout << "Hello, World!" << std::endl; // 输出内容会被立即刷新到终端
// 这里模拟缓冲区中还有未写入的数据
std::cout << "This is a test."; // 这条信息会存储在缓冲区中,未被立即输出
exit(0); //
}
输出结果为:
Hello, World!
This is a test.
这是因为exit可以刷新所有输出流,以确保缓冲区中的数据写入文件或终端。
2、_exit
_exit 是一个系统调用层面的函数,用于立即终止程序,并且不会做任何清理工作。它定义在
void _exit(int status);
status 是一个整数,用来返回程序的退出状态给操作系统。
_exit 的执行流程:
立即终止程序执行。不执行任何文件流的刷新(即缓冲区的内容不会被写入)。不执行通过 atexit() 注册的退出函数。程序直接返回状态码给操作系统。
代码示例:
#include
#include
int main() {
// 使用 std::endl 让输出缓冲区被刷新
std::cout << "Hello, World!" << std::endl; // 输出内容会被立即刷新到终端
// 这里模拟缓冲区中还有未写入的数据
std::cout << "This is a test."; // 此时未使用 std::endl 或 std::flush,因此输出暂存在缓冲区中
// 使用 _exit() 直接退出程序
_exit(0); // 程序立即终止,"This is a test." 不会被显示
}
输出结果:
Hello, World!
产生这个结果的原因是因为程序首先输出 “Hello, World!” 并通过 std::endl 刷新缓冲区,将内容输出到终端。然后,它输出 “This is a test.”,但由于没有使用 std::endl,这条信息被保存在缓冲区中。当 _exit(0) 被调用时,程序直接终止。由于 _exit() 不会刷新缓冲区,“This is a test.” 并不会被写入到终端。
3、总结
exit() 更适用于希望正常结束程序并进行清理工作的情况,而 _exit() 更适合在子进程中快速退出时使用,比如 fork() 之后子进程遇到错误不希望执行任何清理逻辑时。