C++如何在main函数开始之前(或结束之后)执行一段逻辑?
1. 问题 我们知道C/C++程序的执行逻辑是从main函数开始,到main函数结束。但是,有时我们需要在main函数开始之前或结束之后执行一段逻辑,比如:
如何在main函数开始之前执行一段逻辑?
如何在main函数结束之后执行一段逻辑?
有办法实现吗?在往下阅读之前,请先思考一下。
2. 考察的要点 C++程序的代码执行逻辑。 全局变量|静态变量的理解。
3. 解决策略 3.1. 方案一:使用GCC的拓展功能 GCC编译器的拓展功能,通过 __attribute__
关键字注册“在main函数开始之前或结束之后”执行的回调函数。
1 2 3 4 5 6 7 __attribute((constructor)) void before_main () { std::cout << "before main" << std::endl; } __attribute((destructor)) void after_main () { std::cout << "after main" << std::endl; }
3.2. 方案二:使用全局变量 全局变量会在进程刚启动的时候就初始化,在进程结束的时候被销毁。所以:全局对象的初始化会在main函数执行之前被执行;全局对象的销毁会在main函数执行之后被执行。
结合C++类的构造函数和虚构函数的特点,可以专门定义一个类来处理main函数开始之前和结束之后的逻辑(为了保证这个类只有一个全局对象,建议将这个类设计成单例模式),然后在main之前声明这个类的一个全局变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class BeforeAndAfterMain { public : static BeforeAndAfterMain& GetInstance () { static BeforeAndAfterMain instance; return instance; } ~BeforeAndAfterMain () { std::cout << "Global object destory after main" << std::endl; } private : BeforeAndAfterMain () { std::cout << "Global object construct before main" << std::endl; } BeforeAndAfterMain (const BeforeAndAfterMain&) = delete ; BeforeAndAfterMain& operator =(const BeforeAndAfterMain&) = delete ; }; auto & g_before_and_after_main = BeforeAndAfterMain::GetInstance ();
3.3. 方案三:atexit 针对main函数结束之后的逻辑,可以使用atexit函数注册一个回调函数,在main函数执行之后被执行。
1 2 3 4 5 #include <cstdlib> void at_main_exit () { std::cout << "at_main_exit" << std::endl; }
4. Demo测试 4.1. 测试代码 完整测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <cstdlib> __attribute((constructor)) void before_main () { std::cout << "before main" << std::endl; } __attribute((destructor)) void after_main () { std::cout << "after main" << std::endl; } class BeforeAndAfterMain { public : static BeforeAndAfterMain& GetInstance () { static BeforeAndAfterMain instance; return instance; } ~BeforeAndAfterMain () { std::cout << "Global object destory after main" << std::endl; } private : BeforeAndAfterMain () { std::cout << "Global object construct before main" << std::endl; } BeforeAndAfterMain (const BeforeAndAfterMain&) = delete ; BeforeAndAfterMain& operator =(const BeforeAndAfterMain&) = delete ; }; auto & g_before_and_after_main = BeforeAndAfterMain::GetInstance ();void at_main_exit () { std::cout << "at_main_exit" << std::endl; } int main () { atexit (at_main_exit); std::cout << "main begin" << std::endl; int a = 10 ; int b = 5 ; int c = a / b; std::cout << "a /b = " << c << std::endl; std::cout << "main end" << std::endl; return 0 ; }
4.2. 执行结果 1 2 3 4 5 6 7 8 before main Global object construct before main main begin a /b = 2 main end at_main_exit Global object destory after main after main
5. 程序异常退出场景 5.1. 存在的问题 上面的Demo,把
替换成
会导致程序异常(除数不能为0)退出,输出如下:
1 2 3 4 before main Global object construct before main main begin Floating point exception
三种main函数结束后的逻辑均未被执行。说明:程序异常退出时(如:crash),“main函数结束后的逻辑均”不被执行,不能cover住这种场景。
5.2. 解决方案 5.2.1. 原理 当程序崩溃时,操作系统会发送一个信号给程序,通知它发生了异常。在 C++中,可以通过 signal 函数来注册一个信号处理程序,使程序能够在接收到该信号时执行自定义的代码。
程序的执行流程:
执行程序,按正常逻辑执行。
程序崩溃,异常退出,根据不同的崩溃原因,操作系统能识别出不同的崩溃信号(signal)。
操作系统发送对应的崩溃信号(signal)给执行程序。
执行程序根据提前已注册好的信号处理函数,执行对应的信号处理逻辑。
信号处理函数执行完毕,通过exit函数退出程序。
这样保证了:虽然程序的主流程崩溃了,但是程序还是能正常结束。这样即使程序崩溃了,还是能够自己完成如:“资源释放”、“状态保存或重置”等一些重要的逻辑。
5.2.2. 示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void signal_handler (int sig) { std::cerr << "signal_handler" << std::endl; exit (1 ); } int main () { signal (SIGFPE, signal_handler); atexit (at_main_exit); std::cout << "main begin" << std::endl; int a = 10 ; int b = 0 ; int c = a / b; std::cout << "a /b = " << c << std::endl; std::cout << "main end" << std::endl; return 0 ; }
5.2.3. 执行结果 1 2 3 4 5 6 7 before main Global object construct before main main begin signal_handler at_main_exit Global object destory after main after main
5.2.4. 特殊说明
当程序崩溃时,可能已经无法正常执行代码,因此需要谨慎地编写信号处理程序,以避免进一步的崩溃或数据损坏。
信号处理程序执行完成,一定要调用exit退出,否则信号处理函数可能会被循环执行。
考虑各种可能出现的异常信号,比如:SIGSEGV、SIGFPE、SIGILL、SIGABRT等。这些可能出现的异常,都需要注册对应的信号处理程序。以免出现异常漏捕获 的情况。
6. 参考文档 https://blog.csdn.net/zhizhengguan/article/details/122623008 https://blog.csdn.net/MldXTieJiang/article/details/129620160