zoukankan      html  css  js  c++  java
  • 从汇编看c++中全局对象和全局变量

    先来看c++源码:

    #include <iostream>
    using namespace std;
    class X {
    public:
        int i;
    public:
        X(int ii = 0) : i(ii) {
        }
        ~X() {}
    };
    
    X xxx(1);//全局对象
    int i = 2;//全局变量
    
    int main() {
    }

    在代码里面定义了一个全局对象xxx和一个全局变量i,main函数什么也不做。在定义全局对象xxx处打一个断点,然后在vs2010里面调试,查看对象xxx和变量i的内存,如下:
    xxx的内存:

    对象xxx的内存内容都被置为0,而不是无意义的数

    i的内存:

    可以看到,虽然i在对象xxx后面定义,但是,当断点打在定义xxx处时,它内存值已经是2了。这是因为具有初始值的全局变量,其值在连接时就被写入了文件。当用户执行该文件的时候,操作系统分析文件中的数据,将相应数据写入内存之中。因此,全局变量诞生于所在执行文件被操作系统加载之后,执行第一条代码之前。(main函数并不是程序执行的第一条代码)。

    单步跟进后,我们会看到如下的构造对象xxx的汇编码:

        12: X xxx(1);//全局对象
    00BF4450  push        ebp  
    00BF4451  mov         ebp,esp  
    00BF4453  sub         esp,0C0h  
    00BF4459  push        ebx  
    00BF445A  push        esi  
    00BF445B  push        edi  
    00BF445C  lea         edi,[ebp-0C0h]  
    00BF4462  mov         ecx,30h  
    00BF4467  mov         eax,0CCCCCCCCh  
    00BF446C  rep stos    dword ptr es:[edi]  
    00BF446E  push        1  ;压入参数1
    00BF4470  mov         ecx,offset xxx ;获取对象xxx的首地址
    00BF4475  call        X::X ;调用对象xxx的构造函数
    00BF447A  push        offset `dynamic atexit destructor for 'xxx' ;获取对象xxx的析构代理函数地址 ,传递给atexit函数
    00BF447F  call        @ILT+100(_atexit) (0BF1069h)  ;调用atexit函数,注册对象xxx的析构代理函数
    00BF4484  add         esp,4  
    00BF4487  pop         edi  
    00BF4488  pop         esi  
    00BF4489  pop         ebx  
    00BF448A  add         esp,0C0h  
    00BF4490  cmp         ebp,esp  
    00BF4492  call        @ILT+305(__RTC_CheckEsp) (0BF1136h)  
    00BF4497  mov         esp,ebp  
    00BF4499  pop         ebp  
    00BF449A  ret  

    上面汇编码就是全局对象xxx构造代理函数(没有源码对照)汇编码,在代理函数里面,不仅调用了对象xxx的构造函数,而且将xxx的析构函数通过调用atexit函数进行了注册。下面就来看看应用程序调用这个代理函数的过程。

    对于一个应用程序,在调用main函数之前,编译器其实已经做了很多事情,因此,main函数并不是应用程序入口。当应用程序被加载时,入口代码是一个叫mainCRTStartup的函数(通过vs2010的调用堆栈可以看到),这个函数调用_tmainCRTStartup函数,_tmainCRTStartup函数又调用_initterm函数,它的c++源码如下:

    static void __cdecl _initterm ( _PVFV * pfbegin, _PVFV * pfend)
    {
        while ( pfbegin < pfend )
        {
          
            if ( *pfbegin != NULL )
                (**pfbegin)();
            ++pfbegin;
        }
    }

    其中它的参数里面的_PVFV*是一个函数指针数组,编译器为每一个全局对象生成构造代理函数,而构造代理函数的地址就存储在这个函数指针数组里。_PVFV的定义原型如下:

    typedef void(_cdecl *_PVFV)(void);

    从定义可以看出_PVFV指向的构造代理函数为一个无参无返回值的函数。由于代理函数的类型被统一成_PVFV的形式,因此可以通过数组统一管理和执行。
    当mian函数执行完毕之后,由exit来结束进程,从而终止程序的进行。全局对象的析构函数的调用也在其中。上面看到,在调用全局对象xxx的构造代理函数时,将其析构代理函数通过atexit函数进行了注册。因此,退出程序时会以注册相反的顺序调用注册的析构代理函数,并在析构代理函数中调用全局对象的真正析构函数,原理同调用构造代理函数类似。

  • 相关阅读:
    预热buffer pool
    MySQL · 性能优化· InnoDB buffer pool flush策略漫谈
    事务并发控制
    LOAD DATA INFILE – performance case study
    隐式锁
    percona-xtrabackup安装
    mysql 表空间及索引的查看方法
    mysql用户权限
    mysql修改数据库名
    MySQL对innodb某一个表进行移动
  • 原文地址:https://www.cnblogs.com/chaoguo1234/p/3209788.html
Copyright © 2011-2022 走看看