首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

C++如何在main函数开始之前(或结束之后)执行一段逻辑?

编程知识
2024年07月22日 20:44

1. 问题

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

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

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

2. 考察的要点

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

3. 解决策略

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

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

__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之前声明这个类的一个全局变量。

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函数执行之后被执行。

#include <cstdlib>

void at_main_exit(){
    std::cout << "at_main_exit" << std::endl;
}

4. Demo测试

4.1. 测试代码

完整测试代码如下:

#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() {
    // https://en.cppreference.com/w/cpp/header/cstdlib
    atexit(at_main_exit);

    std::cout << "main begin" << std::endl;
    int a = 10;
    int b = 5;
    // crash to exit
    // int b = 0;
    int c = a / b;
    std::cout << "a /b = " << c << std::endl;
    std::cout << "main end" << std::endl;
    return 0;
}

4.2. 执行结果

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,把

    int b = 5;

替换成

    // crash to exit
    int b = 0;

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

before main
Global object construct before main
main begin
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. 示例代码

void signal_handler(int sig) {
    // 这里编写你的异常信号处理逻辑,比如打印日志,保存状态,捕获堆栈信息等。
    std::cerr << "signal_handler" << std::endl;
    // 注意:信号处理程序执行完成,一定要调用exit退出,否则信号处理函数可能会被循环执行。
    exit(1);
}

int main() {
    // 注册信号处理函数
    // signal(SIGSEGV, signal_handler);
    signal(SIGFPE, signal_handler);
    

    // https://en.cppreference.com/w/cpp/header/cstdlib
    atexit(at_main_exit);

    std::cout << "main begin" << std::endl;
    int a = 10;
    // int b = 5;
    // crash to exit
    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. 执行结果

before main
Global object construct before main
main begin
signal_handler
at_main_exit
Global object destory after main
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

From:https://www.cnblogs.com/luoweifu/p/18316999
本文地址: http://shuzixingkong.net/article/304
0评论
提交 加载更多评论
其他文章 OI-Wiki 学习笔记
算法基础 \(\text{Update: 2024 - 07 - 22}\) 复杂度 定义 衡量一个算法的快慢,一定要考虑数据规模的大小。 一般来说,数据规模越大,算法的用时就越长。 而在算法竞赛中,我们衡量一个算法的效率时,最重要的不是看它在某个数据规模下的用时,而是看它的用时随数据规模而增长的趋
OI-Wiki 学习笔记 OI-Wiki 学习笔记 OI-Wiki 学习笔记
2024 Selenium10个替代品
随着自动化测试需求的不断增长,Selenium作为广泛使用的自动化测试工具,虽然功能强大,但也存在一些限制和挑战。在2024年, 越来越多的替代工具涌现,它们提供了更高效、更易用的解决方案。那么,哪些替代品值得我们关注呢? 在自动化测试领域,除了Selenium,还有哪些工具能够满足我们的需求,并且
2024 Selenium10个替代品 2024 Selenium10个替代品 2024 Selenium10个替代品
NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能!
一、写在开头 我们在上一篇博文中提到了Java IO中常见得三大模型(BIO,NIO,AIO),其中NIO是我们在日常开发中使用比较多的一种IO模型,我们今天就一起来详细的学习一下。 在传统的IO中,多以这种同步阻塞的IO模型为主,程序发起IO请求后,处理线程处于阻塞状态,直到请求的IO数据从内核空
NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能! NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能! NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能!
踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境
闲话不多说,具体在windows下下载PCL与解压pcl可以看https://www.yuque.com/huangzhongqing/pcl/这位大佬的文章,那我就具体说一下踩过点坑: 踩坑点1: 按照大佬的文章的步骤进行解压与下载,我的PCL环境下在了K盘中,但是最后不知怎么的我的openni2
踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境 踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境 踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境
新做了一个MySQL 数据库 DDL 差异对比的网站
MySQL 数据库 DDL 差异对比的网站 摘要 新做了个网站,用来对比不同环境下的 DDL 差异,生成变更点和 迁移 DDL 网站地址:https://ddlcompare.com/ 对比过程中如果有问题,可以通过邮箱联系我 huiyuanai709@gmail.com,下班后我会密集的修一波 b
新做了一个MySQL 数据库 DDL 差异对比的网站 新做了一个MySQL 数据库 DDL 差异对比的网站 新做了一个MySQL 数据库 DDL 差异对比的网站
C语言指针易混淆知识点总结
指针 定义 指针是一个变量,存储另一个变量的内存地址,它允许直接访问和操作内存中的数据,使得程序能够以更灵活和高效的方式处理数据和内存。 获取变量地址:使用取地址符 &amp;。 访问地址上的数据:使用解引用符 *。 例子1 指针是存储另一个变量地址的变量。通过使用取地址符 &amp; 和解引用符
typora下载安装以及notepad++下载安装
notepad++下载安装 找到浏览器输入:notepad或者 https://notepad-plus-plus.org/downloads/ 官网下载即可使用 如果官网崩了,可以在微信公众号:A软件安装管家,找到安装notepad++,下载安装即可。 typora下载安装 找到浏览器输入:typ
typora下载安装以及notepad++下载安装 typora下载安装以及notepad++下载安装 typora下载安装以及notepad++下载安装
LM Studio + open-webui 快速本地部署大语言模型
目录一、前言二、环境准备三、安装设置四、下载模型并运行五、配置 open-webui写在结尾 一、前言 自 OpenAi 发布 ChatGPT 对话性大语言模型,AI 这两年发展迎来爆发,国内外也衍生了大量的语言模型开放给公众使用。为了可以让更多人接触到AI,让本地化部署更加轻便快捷,于是就有了Ol
LM Studio + open-webui 快速本地部署大语言模型 LM Studio + open-webui 快速本地部署大语言模型 LM Studio + open-webui 快速本地部署大语言模型