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

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来析构全局对象或静态对象。

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

  1. Java程序的运行机制

    简单来说Java程序的运行机制分为编写.编译和运行三个步骤. 1.编写 编写是指在Java开发环境中进行程序代码的编辑,最终生成后缀名为".java"的Java源文件. 2.编译 ...

  2. java程序代码的运行机制_1.4Java程序的运行机制

    Java 程序的运行必须经过编写.编译和运行 3 个步骤. 编写:是指在 Java 开发环境中进行程序代码的输入,最终形成后缀名为 .java 的 Java 源文件. 编译:是指使用 Java 编译器 ...

  3. java程序的运行机制详细分析

    转自:http://hi.baidu.com/suny_duan/blog/item/074f9afb09f08c9b58ee901b.html JVM(Java虚拟机)一种用于计算设备的规范,可用不 ...

  4. VC++的学习(基于VS2008)——windows程序内部运行机制

    昨天和今天都在学习windows程序的内部运行机制,再次学习这一章,我明显感到条理清晰了,原来这一章是讲我们所用的电脑,这样一个windows平台下程序运行的内部机制的.windows应用程序下最重要 ...

  5. 《VC++深入详解》学习笔记 第一章 Windows程序内部运行机制

    (金光鳞闪影若茫) 窗口四步走: 设计窗口类 注册窗口类 创建窗口 显示更新窗口 最后创建消息循环和响应函数 设计窗口类: typedef struct {UINT style;//窗口类型 WNDP ...

  6. 论Java程序的运行机制

    低级语言就是计算机易于理解而人不易理解的,如汇编语言之类的. 高级语言与之相反易于人理解,接近于自然语言. ,如目前流行的Javac,c++,pascal,python,lisp,prolog,Fox ...

  7. 1.3.2 java程序的运行机制和jvm

    为什么80%的码农都做不了架构师?>>>    java语言比较特殊,由java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是生成一种与平台无关的字节 ...

  8. Java程序的运行机制和JVM

    Java语言比较特殊,由Java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是生成一种与平台无关的字节码(*.class文件).当然,这种字节码不是可执行的,必须使用J ...

  9. 孙鑫-MFC笔记一--Windows程序内部运行机制

    对于一个初学者来说,常常会用到各种库函数,例如printf等等,这些库函数是由你所使用的编译器厂商提供的,在Windows操作系统下,开发的应用程序, 也有这样的库函数,不同的是,这样的库函数,是有w ...

最新文章

  1. Could not download lint-gradle.jar (com.android.tools.lint:lint-gradle:26.4.
  2. ActionContextCleanUp作用
  3. php编程 第一节,PHP第一节php简介_PHP
  4. mysql 主从二进制日志_Mysql-8 配置主从复制(基于二进制日志)
  5. java源代码实例倒计时_Java倒计时三种实现方式代码实例
  6. angluar.js 学习
  7. git 更换本地目录_git 本地库的使用
  8. 51nod-1785:数据流中的算法
  9. Ubuntu Git安装与使用
  10. Rust : Json Web Token
  11. Java二分查找代码
  12. 微软梁念坚:六个新潮流推动IT行业发展
  13. uni-app +vue+微信小程序 发布线上
  14. 基于geoserver的伪三维地图制作
  15. html网页设计作业代码——网上鲜花网页设计(5页)HTML+CSS+JavaScript web期末作业设计网页
  16. 仿微博、微信、qq 点击缩略图, 查看高清图 UI 组件
  17. LSH 近似最近邻查找
  18. pe如何自动加载外置工具_WINPE如何做到启动后自动运行外置程序里面指定的程序...
  19. mybatis带引号_mybatis-MyBatis
  20. 你是否对软件确认测试有了解,如何获取软件确认测试报告?

热门文章

  1. oracle 查询用户所有的序列号
  2. 什么是CSDN开源社区
  3. leetcode 5473. 灯泡开关 IV (阿里云周赛)
  4. Java实现LeetCode第199场周赛(题号5472,5473,5474,5462)
  5. Linux - Unix环境高级编程(第三版) 代码编译
  6. Idea在Maven项目中使用支付宝沙箱环境
  7. 百度地图标记点中添加echarts图表
  8. 数据科学-描述性统计
  9. JS键盘码及使用方法
  10. IIC 通信协议 (二)