静态对象、全局对象与程序的运行机制

2023-11-08

静态对象、全局对象与程序的运行机制

 


1、   在介绍静态对象、全局对象与程序的运行机制之间的关系之前,我们首先看一下atexit函数。

 

atexit函数的声明为:int atexit( void ( __cdecl *func )( void ) );

 

参数为函数指针,返回值为整型,0表示成功,其他表示失败。当程序运行结束时,他调用atexit函数注册的所有函数。如果多次调用atexit函数,那么系统将以LIFO(last-in-first-out)的方式调用所有的注册函数。

 

举例如下(代码摘自MSDN):

 

#include <stdlib.h>

 

#include <stdio.h>

 

 

 

void fn1( void ), fn2( void ), fn3( void ), fn4( void );

 

 

 

void main( void )

 

{

 

  atexit( fn1 );

 

  atexit( fn2 );

 

  atexit( fn3 );

 

  atexit( fn4 );

 

  printf( "This is executed first./n" );

 

}

 

 

 

void fn1()

 

{

 

  printf( "next./n" );

 

}

 

 

 

void fn2()

 

{

 

  printf( "executed " );

 

}

 

 

 

void fn3()

 

{

 

  printf( "is " );

 

}

 

 

 

void fn4()

 

{

 

  printf( "This " );

 

}

 

编译、运行程序后,程序的输出为:

 

This is executed first.

 

This is executed next.

 

注册函数的顺序为:fn1、fn2、fn3、fn4,但是调用顺序为fn4、fn3、fn2、fn1。

 

2、理解了atexit函数之后,我们就可以来看看局部静态对象了。

 

class AAA{ … } ;

 

AAA* createAAA()

 

{

 

        static AAA a ;

 

        return &a ;

 

}

 

在调试状态下,汇编代码如下(请观察蓝色标记出来的代码):

 

AAA* createAAA()

 

{

 

     …

 

     static AAA a ;

 

 

[1] 00401056  call        AAA::AAA (4010A0h)

 

[2] 0040105B  push        offset `createAAA'::`2'::a::`dynamic atexit destructor' (442410h)

 

[3] 00401060  call        atexit (409A50h)

 

00401065  add         esp,4

 

00401068  mov         dword ptr [ebp-4],0FFFFFFFFh

 

     return &a ;

 

0040106F  mov         eax,offset a (452620h)

 

}

 

 

00401091  ret  

 

注:[1]、[2]、[3]为方便说明加入的字符,实际代码中并不存在。

 

[1]语句很明显的调用AAA的构造函数。

 

[2]语句将442410h压入栈中。

 

[3]语句调用atexit函数,根据我们的了解,atexit的参数应该是函数指针。那么我们来分析一下442410h处的代码,从注释来看,我们看到了destructor。代码如下:

 

`createAAA'::`2'::a::`dynamic atexit destructor':

 

 

[1] 0044242E  mov         ecx,offset a (452620h)

 

[2] 00442433  call        AAA::~AAA (403A90h)

 

 

0044244B  ret           

 

[1]语句将a的地址放在ecx寄存器中,这是this调用规范的风格。

 

[2]语句调用AAA的析构函数。

 


程序结束时,将调用atexit函数注册的442410h处的函数,进而调用了AAA的析构函数。从而保证了析构函数的调用。

 

 

3、   了解了局部静态对象之后,我们来看看全局对象。

 

我们知道全局对象必须在main函数前已经被构造。为了弄清楚全局对象何时被构造,我在全局对象的实例化处设置了断点,调用堆栈如下:

 

 

static.exe!aaaa::`dynamic initializer'()  Line 22

 

C++

 

static.exe!_initterm(void (void)* * pfbegin=0x00451038, void (void)* * pfend=0x00451064)  Line 707

 

C

 

static.exe!_cinit(int initFloatingPrecision=1)  Line 208 + 0xf bytes

 

C

 

static.exe!mainCRTStartup()  Line 266 + 0x7 bytes

 

C

 

作为对比,我在AAA的析构函数出设置了断点,调用堆栈如下:

 

 

     static.exe!AAA::~AAA()  Line 19 

 

C++

 

     static.exe!aaaa::`dynamic atexit destructor'()  + 0x28 bytes

 

C++

 

     static.exe!doexit(int code=0, int quick=0, int retcaller=0)  Line 451

 

C

 

     static.exe!exit(int code=0)  Line 311 + 0xd bytes 

 

C

 

     static.exe!mainCRTStartup()  Line 289

 

C

 

由此我们可以看出程序的实际入口点位mainCRTStartup而不是main函数(相对于ANSI的控制台程序而言)。

 

我们来分析一下_cinit函数:

 

注释中有一句[3.  General C initializer routines],看来该函数的功能之一是完成C的初始化例程。

 

函数的核心代码如下:

 

/*

 

         * do initializations

 

         */

 

        initret = _initterm_e( __xi_a, __xi_z );

 

/*

 

         * do C++ initializations

 

         */

 

        _initterm( __xc_a, __xc_z );

 

看来该函数主要进行C、C++的初始化。我们进一步分析函数_initterm_e和_initterm,两个函数的功能进本相同,都是遍历函数指针(由参数指定函数指针的开始位置[__xi_a、__xi_z]、结束位置[__xc_a、__xc_z]),如果函数指针不为null,那么调用该函数。

 

那么__xi_a、__xi_z和__xc_a、__xc_z到底代表了什么呢?在cinitexe.c文件中有如下代码:

 

#pragma data_seg(".CRT$XIA")

 

_CRTALLOC(".CRT$XIA") _PVFV __xi_a[] = { NULL };

 

 

 

#pragma data_seg(".CRT$XIZ")

 

_CRTALLOC(".CRT$XIZ") _PVFV __xi_z[] = { NULL };/* C initializers */

 

 

 

#pragma data_seg(".CRT$XCA")

 

_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL };

 

 

 

#pragma data_seg(".CRT$XCZ")

 

_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL };/* C++ initializers */

 

#pragma comment(linker, "/merge:.CRT=.data")

 

可以看出这四个变量分别在数据段.CRT$XIA、.CRT$XIZ、.CRT$XCA、.CRT$XCZ中。当连接器布局代码时,它按根据的名称,按照字母排序的规则,排列所有段。这样在段.CRT$XIA中的变量出现在段.CRT$XIZ所有变量之前,从而形成链表。对于.CRT$XCA、.CRT$XCZ数据段同理。最后这四个数据段被合并到.data数据段中。

 

再看看这些变量的类型,typedef void (__cdecl *_PVFV)(void); 所以这些变量组成了2个初始化函数指针链表。

 

调试过程中,看到__xc_a、__xc_z链表中,指向的初始化函数很多是构造函数,如:

 

static std::_Init_locks  initlocks;

 

static filebuf fout(_cpp_stdout);

 

extern _CRTDATA2 ostream cout(&fout);

 

cout对象也在此时被构造。

 

对于析构函数的调用也是采用相同的方式,只是此时每一种初始化,都有一种终止函数与之对应。

4、  总结

 

l         编译、连接程序时,编译器将所有全局对象的初始化函数放入.CRT$Xx中,连接器将所有的.CRT$XCx段合并成为.rdata数据段。在.CRT$XCA 到 .CRT$XCZ的所有段的数据组成初始化函数指针列表。

 

l   函数执行时,_initterm( __xc_a, __xc_z )函数调用所有的初始化函数。构造全局对象。构造对象完毕,调用atexit函数来保证析构函数的调用。Modern C++ Design就是通过控制调用atexit函数来决定对象的析构顺序的。

 

l   对于静态对象使用atexit来保证析构函数的调用。

 

l    程序结束时,调用exit来析构全局对象或静态对象。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

静态对象、全局对象与程序的运行机制 的相关文章

随机推荐

  • JavaScript 中 undefined 与 undeclared 之间的区别?

    认为这个根本的区别在于 undefined是Js语言类型 而undeclared却是一种Js语法错误吧 解答第一部分 在Js中 有两个表示 空 的值undefined和null 其中比较有用的是 undefined undefined 是一
  • spring boot报FileSizeLimitExceededException异常的解决方法

    开发spring boot程序时 遇到了如下错误 The field file exceeds its maximum permitted size of 1048576 bytes 原因 Spring Boot工程嵌入的tomcat限制了
  • 探索Java8——测试Lambda表达式

    文章目录 测试Lambda表达式 测试可见Lambda行为 测试使用Lambda的方法的行为 调试 测试Lambda表达式 通常而言 好的软件工程实践一定少不了单元测试 借此保证程序的行为与预期一致 你编写测试用例 通过这些测试用例确保你代
  • FISCO BCOS(二十九)———区块链浏览器

    一 浏览器主要用途 区块链浏览器将区块链中的数据可视化 并进行实时展示 方便用户以Web页面的方式 获取当前区块链中的信息 二 一键部署 2 1 环境要求 环境 版本 Java JDK8或以上版本 MySQL MySQL 5 6或以上版本
  • UE4-VaRest客户端上传玩家得分并获取排行榜

    排行榜相关 1 上传得分 2 获取玩家排名 3 获取排行榜 1 上传得分 2 获取玩家排名 3 获取排行榜
  • 一个环境变量导致运行报错Exception in thread "main" java.lang.NoClassDefFoundError:

    昨天下午编译通过就一直报错Exception in thread main java lang NoClassDefFoundError 早上终于调通了 加油 1 安装与配置 1 在我的电脑C盘里安装了SDK 路径是这样的 C Progra
  • 如何卸载显卡驱动通过软件简单卸载法

    有时候需要卸载显卡的驱动时 可以通过下载这个软件来卸载 显卡驱动卸载工具 Display Driver Uninstaller 简称为DDU卸载工具 下载地址为 Official Display Driver Uninstaller DDU
  • Python爬虫爬取VIP网站

    一直有爱看美剧的习惯 一方面锻炼一下英语听力 一方面打发一下时间 之前是能在视频网站上面在线看的 可是自从广电总局的限制令之后 进口的美剧英剧等貌似就不在像以前一样同步更新了 但是 作为一个宅diao的我又怎甘心没剧追呢 所以网上随便查了一
  • 信息系统分析相关知识梳理

    一 企业信息化战略与实施 一 企业信息化 1 以数据处理为核心 围绕职能部门 有企业系统规划法 关键成功因素法 战略集合转化法 2 以企业内部管理信息系统为核心 围绕企业整体 有战略数据规划法 信息工程法 战略栅格法 3 以集成为核心 面向
  • AD20画板基本流程

    AD20画板流程 前言 一 AD库的选择与添加 二 画原理图 1 选择元器件 2 放置端口和放置线 3 标注 4 分离模块 5 将原理图更新到PCB中 1 元器件未封装 2 网络标签的网络属性没有放在放置线上 3 网络标签没有对应上 三 P
  • ESP系列模组PCB设计及天线摆放

    ESP8266 8285系列可以焊接到PCB板上 为了使模组获得最佳的射频性能 要合理设计模组和天线在底板上的摆放位置 一 布局 1 天线布局 二 电路设计 三 地层
  • JAVA开发运维(web场景漏洞与修复)

    漏洞一 fastjson lt 1 2 80 反序列化任意代码执行漏洞 修复建议 1 升级到最新版本1 2 83 https github com alibaba fastjson releases tag 1 2 83 该版本涉及auto
  • 做了一个directshow的filter,把RGB视频流变成黑白的 .

    转自 http blog csdn net mengaim cn article details 241449 做的这个directshow的filter属于transform filter 在其间 参考了 directshow的帮助文档
  • vt 在ubuntu交叉编译windows的执行文件

    参考文档 见官网 系统 ubuntu18 04 编译步骤 1 安装 goclang 下载 https golang org doc install 解压命令 tar C usr local xzf go1 14 2 linux amd64
  • Connection is read-only. Queries leading to data modification are not allowed

    看了下mysql connector 5 1 40版本中 如果设置failoverReadOnly true 即默认值 参考 链接 当mysql连接failover时 会根据jdbc连接串将当前连接的readOnly值设置为true 第8行
  • Matlab之行列向量的赋值

    在别人的matlab代码中看到 将列向量赋值给行向量 最初还以为是别人的代码有bug 实际上运行后才发现是由自己的无知造成的 因此 将如下一小段测试的代码贴出来 向量的维度由左值 被赋值的变量 决定 column 1 2 3 row 0 0
  • python程序设计报告-20191206 实验二《Python程序设计》实验报告

    学号 2019 2020 2 Python程序设计 实验二报告 课程 Python程序设计 班级 1912 姓名 陈发强 学号 20191206 实验教师 王志强 实验日期 2020年4月19日 必修 选修 公选课 1 实验内容 设计并完成
  • 7-7 12-24小时制 (15 分)

    编写一个程序 要求用户输入24小时制的时间 然后显示12小时制的时间 输入格式 输入在一行中给出带有中间的 符号 半角的冒号 的24小时制的时间 如12 34表示12点34分 当小时或分钟数小于10时 均没有前导的零 如5 6表示5点零6分
  • 雷达系统与信号处理概述(一)

    第一章 雷达系统与信号处理概述 一 目录 第一章 雷达系统与信号处理概述 一 1 1 雷达的基本功能 1 2 检测 跟踪 成像概述 1 3 检测概率和虚警概率 1 3 1检测概率 1 3 2虚警概率 1 4 雷达分辨率 1 4 1 雷达距离
  • 静态对象、全局对象与程序的运行机制

    静态对象 全局对象与程序的运行机制 1 在介绍静态对象 全局对象与程序的运行机制之间的关系之前 我们首先看一下atexit函数 atexit函数的声明为 int atexit void cdecl func void 参数为函数指针 返回值