• 深入C++的默认构造函数1


          总所周知,构造函数是对象重要的组成部分,承担了对象的初始化工作。本文主要讲C++下对象的默认构造函数的反汇编代码,或许,这没什么用处,但是,知其然,还要知其所以然吧,了解底层,将对我们更好地掌握知识有很大帮助。打牢基础,将更有利于我们的成长。“勿在浮沙筑高台”-------侯捷。

         当一个对象没有声明构造函数的时候,编译器会暗中为对象生成一个默认构造函数(Default Constructor),被暗中生成的的默认构造函数将是一个trivial(无用的,浅薄无能的)的函数。(摘自“深度探索C++对象模型 P40”)。下面我们来看看编译器生成的默认构造函数。

    例子1:

    class A
    {
    public:
        void print(){};
    };
    
    int main()
    {
        A a;
        a.print();
        return 0;
    };

    在vs2008下main方法的汇编代码如下所示(按F5进入调试模式,之后“Alt+8”即可跳出代码和汇编的混合代码):

    int main()
      {
      013A13A0  push        ebp  
      013A13A1  mov         ebp,esp 
      013A13A3  sub         esp,0CCh 
      013A13A9  push        ebx  
      013A13AA  push        esi  
      013A13AB  push        edi  
      013A13AC  lea         edi,[ebp-0CCh] 
      013A13B2  mov         ecx,33h 
      013A13B7  mov         eax,0CCCCCCCCh 
      013A13BC  rep stos    dword ptr es:[edi] 
          A a;
          a.print();
      013A13BE  lea         ecx,[a] 
      013A13C1  call        A::print (13A100Fh) 
          return 0;
      013A13C6  xor         eax,eax 
      };

     下面是这段汇编代码的解释,参考:http://johnlxj.diandian.com/?tag=edi:

    push ebp;将ebp压入栈,ebp主要用来存储当前栈帧的栈底指针,所有的局部变量都是用相对于ebp的偏移来使用的。
    
    move ebp,esp
    
    sub esp,oCCh;这两句的意思是是将当前栈顶作为这一栈帧的基值。并且为本函数分配了0CCh*4个字节的栈空间。因为栈顶往上走了cc步。
    
    push ebx
    
    push esi
    
    push edi;这三句的意思是将上个函数所使用的寄存器的值压入栈中,即保存上个方法所使用的寄存器值,为了执行完本函数后能返回去。
    
    lea edi, [ebp-0CCh]
    
    mov ecx,33h
    
    mov eax,0CCCCCCCCh
    
    rep stos dword ptr es:[edi]; 上面4句代码的意思是将ebp到0CCh的栈空间(33h * 4字节)通过rep(循环指令)置为0CCh。

    通过上面的汇编,我们可以知道对于一个这样简单的类(只声明了一个方法的类),编译器生成的代码甚至没有调用它的构造函数(应该编译器没有暗中合成它的构造函数)。

    例子2:

    当我们将A的声明改为:

    class A
    {
    public:
    
    A(){};
    void print(){};
    };

    反汇编的代码如下所示:

    int main()
    {
    00B613B0  push        ebp  
    00B613B1  mov         ebp,esp 
    00B613B3  sub         esp,0CCh 
    00B613B9  push        ebx  
    00B613BA  push        esi  
    00B613BB  push        edi  
    00B613BC  lea         edi,[ebp-0CCh] 
    00B613C2  mov         ecx,33h 
    00B613C7  mov         eax,0CCCCCCCCh 
    00B613CC  rep stos    dword ptr es:[edi] 
        A a;
    00B613CE  lea         ecx,[a] 
    00B613D1  call        A::A (0B611D6h) 
        a.print();
    00B613D6  lea         ecx,[a] 
    00B613D9  call        A::print (0B6100Fh) 
        return 0;
    00B613DE  xor         eax,eax 
    };

    A的构造函数反汇编如下:

      A(){};
    00B61440  push        ebp 
    00B61441  mov         ebp,esp 
    00B61443  sub         esp,0CCh 
    00B61449  push        ebx  
    00B6144A  push        esi  
    00B6144B  push        edi  
    00B6144C  push        ecx  
    00B6144D  lea         edi,[ebp-0CCh] 
    00B61453  mov         ecx,33h 
    00B61458  mov         eax,0CCCCCCCCh 
    00B6145D  rep stos    dword ptr es:[edi] 
    00B6145F  pop         ecx  
    00B61460  mov         dword ptr [ebp-8],ecx 
    00B61463  mov         eax,dword ptr [this] 
    00B61466  pop         edi  
    00B61467  pop         esi  
    00B61468  pop         ebx  
    00B61469  mov         esp,ebp 
    00B6146B  pop         ebp  
    00B6146C  ret

    对照前面的解释,A::A()是啥事都没做(在将申请的栈空间置为0后就开始将退出函数),这段汇编代码或许说明我们声明个空构造函数是件没意义的事(当然,带有成员初始化列表的空构造函数不算)。

    例子3:

    下面再给类的定义添些东西:

    class A
    {
    public:
    
    A(){};
    void print(){};
    
    private:
    
    int m_nValue;
    };

    直接贴它的构造函数:

      A(){};
    00E21420  push        ebp  
    00E21421  mov         ebp,esp 
    00E21423  sub         esp,0CCh 
    00E21429  push        ebx  
    00E2142A  push        esi  
    00E2142B  push        edi  
    00E2142C  push        ecx  
    00E2142D  lea         edi,[ebp-0CCh] 
    00E21433  mov         ecx,33h 
    00E21438  mov         eax,0CCCCCCCCh 
    00E2143D  rep stos    dword ptr es:[edi] 
    00E2143F  pop         ecx  
    00E21440  mov         dword ptr [ebp-8],ecx 
    00E21443  mov         eax,dword ptr [this] 
    00E21446  pop         edi  
    00E21447  pop         esi  
    00E21448  pop         ebx  
    00E21449  mov         esp,ebp 
    00E2144B  pop         ebp  
    00E2144C  ret

    和例子2的构造函数对比,代码完全一样,这说明类中的成员变量在初始化的时候需要是一个特定值的话,应该自己手动初始化,编译器是不会负责这个事情的。但是,当我们类中有成员变量的时候,类构造的时候最好将成员变量初始化相应的初值,这是个良好的编程习惯,这次就先说到这里了,复杂的默认构造函数(成员变量有Default构造函数的默认构造函数,继承下的构造函数,有虚函数的构造函数)的反汇编会在下面的系列继续。

    这是我第一次写博客,请大家多多指导,谢谢大家。

  • 相关阅读:
    ES6你不知道的let关键字及变量的提升
    [ts] Property 'aaa' does not exist on type 'Window' 解决办法
    EXPRESS项目PM2启动NODE_ENV传参数不生效问题解决方法
    react组件开发规范总结
    JavaScript heap out of memory解决方法
    移动端响应式布局--你不知道的CSS3.0媒体查询,解决rem部分情况下无法适配的场景
    在nodeJs的Express框架下用TypeScript编写router路由出现import关键字错误的解决方案
    Zepto的天坑汇总
    mac下CornerstoneSVN出错 Description : The working copy is locked due to a previous error
    bootstrap的popover插件在focus模式时在Safari浏览器无法使用的bug解决方案
  • 原文地址:https://www.cnblogs.com/yetuweiba/p/2646482.html
Copyright © 2020-2023  润新知