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

C++如何在main函数开始之前(或结束之后)执行一段逻辑?的更多相关文章

  1. C/C++中如何在main()函数之前执行一条语句?

    在C语言中,如果使用GCC的话,可以通过attribute关键字声明constructor和destructor(C语言中如何在main函数开始前执行函数) #include <stdio.h& ...

  2. 多玩YY语音的面试题:C++中如何在main()函数之前执行操作?

    多玩YY语音的面试题:C++中如何在main()函数之前执行操作? 第一反应main()函数是所有函数执行的开始.但是问题是main()函数执行之前如何执行呢? 联想到MFC里面的 C**App类的t ...

  3. jquery动画函数里面可以跟一个回调函数,表示动画结束后执行的代码

    jquery动画函数里面可以跟一个回调函数,表示动画结束后执行的代码 使用js监听动画结束后进行的操作: $ele.fadeIn(300,function(){...}) $ele.fadeOut(3 ...

  4. Java静态变量、静态块、构造块、构造函数、main函数、普通代码块的执行顺序

    测试代码 public class SingleTest { public static String v = "StaticValue"; static { System.out ...

  5. 如何在Spring Boot应用启动之后立刻执行一段逻辑

    1. 前言 不知道你有没有接到这种需求,项目启动后立马执行一些逻辑.比如简单的缓存预热,或者上线后的广播之类等等.如果你使用 Spring Boot 框架的话就可以借助其提供的接口CommandLin ...

  6. C++ main()函数及其参数

    1.首先,想想C/C++在main函数之前和之后会做些什么? 我们看看底层的汇编代码: __start: : init stack; init heap; open stdin; open stdou ...

  7. main函数解析

    原文链接:http://parisliu2008.blog.163.com/blog/static/95070867200951510412959/ main参数 2009-06-15 10:41:2 ...

  8. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  9. SequoiaDB 系列之五 :源码分析之main函数

    好久好久没有写博客了,因为一直要做各种事,工作上的,生活上的,这一下就是半年. 时光如梭. 这两天回头看了看写的博客,感觉都是贻笑大方. 但是还是想坚持把SequoiaDB系列写完. 初步的打算已经确 ...

  10. golang中的init函数以及main函数

    首先我们看一个例子:init函数: init 函数可在package main中,可在其他package中,可在同一个package中出现多次. main函数 main 函数只能在package ma ...

随机推荐

  1. IceRPC之多路复用传输>快乐的RPC

    作者引言 很高兴啊,我们来到了IceRPC之多路复用传输>快乐的RPC,基础引导,打好基础,才能让自已不在迷茫,快乐的畅游世界. icerpc 和多路复用传输 了解 icerpc 协议和多路复用 ...

  2. 前端项目npm install安装报错:code ERESOLVE ERESOLVE could not resolve

    背景:使用npm 安装依赖的时候,发现报了如下的错误: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tr ...

  3. NOIP模拟101(多校33)

    T1 ladice 解题思路 我们把一个物品看做 \(A_i\) 与 \(B_i\) 之间的连边. 那么如果加入这条边之后联通块中有超过两个环或者两个环就是不合法的,也就是合法的状态只能是一个基环树和 ...

  4. 基于React的SSG静态站点渲染方案

    基于React的SSG静态站点渲染方案 静态站点生成SSG - Static Site Generation是一种在构建时生成静态HTML等文件资源的方法,其可以完全不需要服务端的运行,通过预先生成静 ...

  5. k8s——集群环境问题合集

    创建集群 k8s集群创建 集群环境问题合集 重置master节点 kubeadm reset -f # -f 强制重置 可选 重置node节点 # 当你的master节点重置后,node节点需要重新加 ...

  6. 电脑网卡把报文的vlan tag去掉

    1 现象 现象说明:从电脑的网卡(用的Realtek)进来一个带vlan tag的报文,但是使用wireshark抓取的报文没有vlan tag. 解决方式如下:需要注册表. 参考链接1:https: ...

  7. 解决java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone

    错误描述: 使用JDBC连接数据库是产生错误 应该是数据库时区问题,在url配置时设置serverTimezone = GMT即可 url = "jdbc:mysql://localhost ...

  8. kettle从入门到精通 第十四课 kettle kafka 生产者和消费者

    1.本节课讲解kafka生产者和消费者两个步骤.这两个组件可以实现数据实时同步(后续课程会讲解). 2.kafka producer 步骤 1)step name:自定义名称 2)connection ...

  9. C#.NET 国密SM4加密解密 CBC ECB 2种模式

    注意点: 1.加密时,明文转 byte[] 时,不要用 Encoding.Default,一定要指定编码,如:UTF-8. 解密时,解出的 byte[] 转 string 同样要指定相同的编码. 2. ...

  10. Kubernetes监控手册05-监控Kubelet

    上一篇我们介绍了如何监控Kube-Proxy,Kube-Proxy的/metrics接口没有认证,相对比较容易,这一篇我们介绍一下Kubelet,Kubelet的监控相比Kube-Proxy增加了认证 ...