进程退出全解析:正常退出、异常终止与 exit/_exit 区别

进程退出全解析:正常退出、异常终止与 exit/_exit 区别

一、进程退出场景

在编写 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 是一个全局变量,定义在 头文件中。它用于存储上一个系统调用或库函数出错时的错误码。当某些系统调用或标准库函数(例如 open(), read(), malloc() 等)失败时,它会将错误码存储在 errno 中。errno 是线程局部的,这意味着每个线程都有自己的 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++ 标准库中定义的一个函数,用于终止程序的执行,并且可以返回一个状态码给操作系统。它定义在 (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 是一个系统调用层面的函数,用于立即终止程序,并且不会做任何清理工作。它定义在 头文件中。在调用 _exit 时,程序会立刻退出,不会执行任何文件流的刷新,也不会调用通过 atexit() 注册的函数。

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() 之后子进程遇到错误不希望执行任何清理逻辑时。

相关文章

Vutlr新手入门指南:方案推荐、机房选择、优惠信息和购买教程
为什么要叫吴亦凡粉丝鲵(粉丝叫吴亦凡什么外号)
365bet网址多少

为什么要叫吴亦凡粉丝鲵(粉丝叫吴亦凡什么外号)

⌛ 07-07 👁️‍🗨️ 1179
TCL空调和格力空调哪个好
365bet官网网址多少

TCL空调和格力空调哪个好

⌛ 09-04 👁️‍🗨️ 5790
边境之旅浣熊刷新地图 边境之旅浣熊哪里多
365bet官网网址多少

边境之旅浣熊刷新地图 边境之旅浣熊哪里多

⌛ 07-12 👁️‍🗨️ 3153
追书大师打不开了解决方法
365bet官网网址多少

追书大师打不开了解决方法

⌛ 09-03 👁️‍🗨️ 4255
如何正确选择微单相机的配件?微单相机配件选择小技巧
365bet官网网址多少

如何正确选择微单相机的配件?微单相机配件选择小技巧

⌛ 08-03 👁️‍🗨️ 5269