经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++如何在main函数开始之前(或结束之后)执行一段逻辑?
来源:cnblogs  作者:陌尘(MoChen)  时间:2024/7/23 9:07:47  对本文有异议

1. 问题

我们知道C/C++程序的执行逻辑是从main函数开始,到main函数结束。但是,有时我们需要在main函数开始之前或结束之后执行一段逻辑,比如:

  1. 如何在main函数开始之前执行一段逻辑?
  2. 如何在main函数结束之后执行一段逻辑?

有办法实现吗?在往下阅读之前,请先思考一下。

2. 考察的要点

C++程序的代码执行逻辑。
全局变量|静态变量的理解。

3. 解决策略

3.1. 方案一:使用GCC的拓展功能

GCC编译器的拓展功能,通过 __attribute__ 关键字注册“在main函数开始之前或结束之后”执行的回调函数。

  1. __attribute((constructor)) void before_main() {
  2. std::cout << "before main" << std::endl;
  3. }
  4. __attribute((destructor)) void after_main() {
  5. std::cout << "after main" << std::endl;
  6. }

3.2. 方案二:使用全局变量

全局变量会在进程刚启动的时候就初始化,在进程结束的时候被销毁。所以:全局对象的初始化会在main函数执行之前被执行;全局对象的销毁会在main函数执行之后被执行。

结合C++类的构造函数和虚构函数的特点,可以专门定义一个类来处理main函数开始之前和结束之后的逻辑(为了保证这个类只有一个全局对象,建议将这个类设计成单例模式),然后在main之前声明这个类的一个全局变量。

  1. class BeforeAndAfterMain
  2. {
  3. public:
  4. static BeforeAndAfterMain& GetInstance()
  5. {
  6. static BeforeAndAfterMain instance;
  7. return instance;
  8. }
  9. ~BeforeAndAfterMain()
  10. {
  11. std::cout << "Global object destory after main" << std::endl;
  12. }
  13. private:
  14. BeforeAndAfterMain()
  15. {
  16. std::cout << "Global object construct before main" << std::endl;
  17. }
  18. BeforeAndAfterMain(const BeforeAndAfterMain&) = delete;
  19. BeforeAndAfterMain& operator=(const BeforeAndAfterMain&) = delete;
  20. };
  21. auto& g_before_and_after_main = BeforeAndAfterMain::GetInstance();

3.3. 方案三:atexit

针对main函数结束之后的逻辑,可以使用atexit函数注册一个回调函数,在main函数执行之后被执行。

  1. #include <cstdlib>
  2. void at_main_exit(){
  3. std::cout << "at_main_exit" << std::endl;
  4. }

4. Demo测试

4.1. 测试代码

完整测试代码如下:

  1. #include <iostream>
  2. #include <cstdlib>
  3. __attribute((constructor)) void before_main() {
  4. std::cout << "before main" << std::endl;
  5. }
  6. __attribute((destructor)) void after_main() {
  7. std::cout << "after main" << std::endl;
  8. }
  9. class BeforeAndAfterMain
  10. {
  11. public:
  12. static BeforeAndAfterMain& GetInstance()
  13. {
  14. static BeforeAndAfterMain instance;
  15. return instance;
  16. }
  17. ~BeforeAndAfterMain()
  18. {
  19. std::cout << "Global object destory after main" << std::endl;
  20. }
  21. private:
  22. BeforeAndAfterMain()
  23. {
  24. std::cout << "Global object construct before main" << std::endl;
  25. }
  26. BeforeAndAfterMain(const BeforeAndAfterMain&) = delete;
  27. BeforeAndAfterMain& operator=(const BeforeAndAfterMain&) = delete;
  28. };
  29. auto& g_before_and_after_main = BeforeAndAfterMain::GetInstance();
  30. void at_main_exit(){
  31. std::cout << "at_main_exit" << std::endl;
  32. }
  33. int main() {
  34. // https://en.cppreference.com/w/cpp/header/cstdlib
  35. atexit(at_main_exit);
  36. std::cout << "main begin" << std::endl;
  37. int a = 10;
  38. int b = 5;
  39. // crash to exit
  40. // int b = 0;
  41. int c = a / b;
  42. std::cout << "a /b = " << c << std::endl;
  43. std::cout << "main end" << std::endl;
  44. return 0;
  45. }

4.2. 执行结果

  1. before main
  2. Global object construct before main
  3. main begin
  4. a /b = 2
  5. main end
  6. at_main_exit
  7. Global object destory after main
  8. after main

5. 程序异常退出场景

5.1. 存在的问题

上面的Demo,把

  1. int b = 5;

替换成

  1. // crash to exit
  2. int b = 0;

会导致程序异常(除数不能为0)退出,输出如下:

  1. before main
  2. Global object construct before main
  3. main begin
  4. Floating point exception

三种main函数结束后的逻辑均未被执行。说明:程序异常退出时(如:crash),“main函数结束后的逻辑均”不被执行,不能cover住这种场景。

5.2. 解决方案

5.2.1. 原理

当程序崩溃时,操作系统会发送一个信号给程序,通知它发生了异常。在 C++中,可以通过 signal 函数来注册一个信号处理程序,使程序能够在接收到该信号时执行自定义的代码。

程序的执行流程:

  1. 执行程序,按正常逻辑执行。
  2. 程序崩溃,异常退出,根据不同的崩溃原因,操作系统能识别出不同的崩溃信号(signal)。
  3. 操作系统发送对应的崩溃信号(signal)给执行程序。
  4. 执行程序根据提前已注册好的信号处理函数,执行对应的信号处理逻辑。
  5. 信号处理函数执行完毕,通过exit函数退出程序。

这样保证了:虽然程序的主流程崩溃了,但是程序还是能正常结束。这样即使程序崩溃了,还是能够自己完成如:“资源释放”、“状态保存或重置”等一些重要的逻辑。

5.2.2. 示例代码

  1. void signal_handler(int sig) {
  2. // 这里编写你的异常信号处理逻辑,比如打印日志,保存状态,捕获堆栈信息等。
  3. std::cerr << "signal_handler" << std::endl;
  4. // 注意:信号处理程序执行完成,一定要调用exit退出,否则信号处理函数可能会被循环执行。
  5. exit(1);
  6. }
  7. int main() {
  8. // 注册信号处理函数
  9. // signal(SIGSEGV, signal_handler);
  10. signal(SIGFPE, signal_handler);
  11. // https://en.cppreference.com/w/cpp/header/cstdlib
  12. atexit(at_main_exit);
  13. std::cout << "main begin" << std::endl;
  14. int a = 10;
  15. // int b = 5;
  16. // crash to exit
  17. int b = 0;
  18. int c = a / b;
  19. std::cout << "a /b = " << c << std::endl;
  20. std::cout << "main end" << std::endl;
  21. return 0;
  22. }

5.2.3. 执行结果

  1. before main
  2. Global object construct before main
  3. main begin
  4. signal_handler
  5. at_main_exit
  6. Global object destory after main
  7. after main

5.2.4. 特殊说明

  1. 当程序崩溃时,可能已经无法正常执行代码,因此需要谨慎地编写信号处理程序,以避免进一步的崩溃或数据损坏。
  2. 信号处理程序执行完成,一定要调用exit退出,否则信号处理函数可能会被循环执行。
  3. 考虑各种可能出现的异常信号,比如:SIGSEGV、SIGFPE、SIGILL、SIGABRT等。这些可能出现的异常,都需要注册对应的信号处理程序。以免出现异常漏捕获的情况。

6. 参考文档

https://blog.csdn.net/zhizhengguan/article/details/122623008
https://blog.csdn.net/MldXTieJiang/article/details/129620160

原文链接:https://www.cnblogs.com/luoweifu/p/18316999

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号