zoukankan      html  css  js  c++  java
  • 浅议C++/CLI的gcnew关键字

      C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:

      1.     gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址.
      2.     gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放.
     
      当然,从程序员的角度来说,管它是句柄还是什么其他的东西,总跑不掉是对某块内存地址的引用,实际上我们都可以理解成指针.下面我们就写一段代码来测试一下好了.
     
    using namespace System;
     
    ref class Foo
    {
    public:
        Foo()
        {
           System::Console::WriteLine("Foo::Foo");
        }
        ~Foo()
        {
           System::Console::WriteLine("Foo::~Foo");
        }
    public:
        int m_iValue;
    };
     
    int _tmain()
    {
        int* pInt = new int;
        int^ rInt = gcnew int;
        Foo^ rFoo = gcnew Foo;
     
        delete rFoo;
        delete rInt;
        delete pInt;
    }
     
      我把调试的时候JIT编译的汇编代码择录了部分如下显示(请注意红色部分):

        int* pInt = new int;
    0000004c  mov         ecx,4
    00000051  call        dword ptr ds:[03B51554h]
    00000057  mov         esi,eax
    00000059  mov         dword ptr [esp+18h],esi
        int^ rInt = gcnew int;
    0000005d  mov         ecx,788EF9D8h
    00000062  call        FCFAF66C
    00000067  mov         esi,eax
    00000069  mov         dword ptr [esi+4],0
    00000070  mov         edi,esi
        Foo^ rFoo = gcnew Foo;
    00000072  mov         ecx,3B51768h
    00000077  call        FCFAF66C
    0000007c  mov         esi,eax
    0000007e  mov         ecx,esi
    00000080  call        dword ptr ds:[03B517ACh]
    00000086  mov         dword ptr [esp+1Ch],esi
     
        delete rFoo;
    0000008a  mov         ebx,dword ptr [esp+1Ch]
    0000008e  test        ebx,ebx
    00000090  je          000000A4
    00000092  mov         ecx,ebx
    00000094  call        dword ptr ds:[03FD0028h]
    0000009a  mov         dword ptr [esp+14h],0
    000000a2  jmp         000000AC
    000000a4  mov         dword ptr [esp+14h],0
        delete rInt;
    000000ac  mov         edx,edi
    000000ae  mov         ecx,788F747Ch
    000000b3  call        FC8D20FD
    000000b8  mov         ebp,eax
    000000ba  test        ebp,ebp
    000000bc  je          000000D0
    000000be  mov         ecx,ebp
    000000c0  call        dword ptr ds:[03FD0020h]
    000000c6  mov         dword ptr [esp+10h],0
    000000ce  jmp         000000D8
    000000d0  mov         dword ptr [esp+10h],0
        delete pInt;
    000000d8  mov         ecx,dword ptr [esp+18h]
    000000dc  call        dword ptr ds:[03B51540h]
     
     
       我们先看分配内存这部分的代码
     
      1.调用new方式分配
    int* pInt = new int;
    0000004c  mov         ecx,4
    00000051  call        dword ptr ds:[03B51554h]

      可以看到,和以前在vc6中一样,分配内存的步骤如下:
      1.  首先把sizeof(int) = 4 放到ecx中
      2.  调用operator new 去分配4个字节
      3.  调用构造函数等等......(这里不是我们的重点)

      成功分配后,会把返回地址放在eax中。
     
      2.调用gcnew方式分配
        int^ rInt = gcnew int;
    0000005d  mov         ecx,788EF9D8h
    00000062  call        FCFAF66C
    。。。
        Foo^ rFoo = gcnew Foo;
    00000072  mov         ecx,3B51768h
    00000077  call        FCFAF66C

      可以看到gcnew也是通过把一个参数放到ecx中,然后再调用一个函数来完成分配的操作,显然0x788EF9D8应该是一个地址,而不可能是一个数值。我们可以看到这里gcnew创建两个不同类型的变量,调用的函数地址却都是0xFCFAF66C,而存放到ecx中的两个地址就不一样。究竟这几个地址代表什么呢?
     
      和new一样gcnew也是把返回地址放在eax中。我们直接从内存窗口看eax指向的内存块好了。Aha,看到了没有?

      这次的eax = 0x00F73404  对应的内存块为
     
    0x00F73404  d8 f9 8e 78 00 00 00 00 。。。
     
      这个不就是 mov 到 ecx中的值么?再回忆昨天写的分析Object对象布局的文章,可以肯定这个就是 MethodTable地址了,对于这个int来说,后面的4个字节对应的就是存放它的RawData,比如如果你初始化为 4 那么内存对应的就变化为 d8 f9 8e 79 04 00 00 00
     
      分析清楚存放到ecx中的是 MethodTable指针,我们再分析那个对应的call函数,从vm的代码可以看出,有三个全局函数用来根据MethodTable创建对象,同时MethodTable本身也提供一个成员函数Allocate(),只不过这个成员函数也是调用的下面的函数:

    OBJECTREF AllocateObject( MethodTable *pMT )
    OBJECTREF AllocateObjectSpecial( MethodTable *pMT )
    OBJECTREF FastAllocateObject( MethodTable *pMT )
     
      其中AllocateObject又是调用AllocateObjectSpecial来完成工作。那么我们调用的应该就是AllocateObject或者FastAllocateObject了。

      在我们的例子里面两个call的地址都一样,但是你如果写下代码 double ^ pDouble = gcnew double;这个时候的地址是多少?它和int 的一样么?

      目前我还没有仔细去研究这个地址到底对应的是该类型的MethodTable::Allocate()或是上面的这三个全局函数,如果对应MethodTable::Allocate(),那么2.0中应该有个MethodTable::FastAllocate()吧,否则应该就是对应的全局函数AllocateObject 以及FastAllocateObject了。过几天一定要抽空再好好研究一下。
     
      下面看对应的delete函数。
        delete pInt;
    000000d8  mov         ecx,dword ptr [esp+18h]
    000000dc  call        dword ptr ds:[03B51540h]
     
    比较简单,就是传入地址,然后调用operator delete来释放类存,会调用析构函数
     
      对应的,释放gcnew创建的对象的代码如下:
        delete rInt;
    000000ac  mov         edx,edi
    000000ae  mov         ecx,788F747Ch
    000000b3  call        FC8D20FD

      这个也相对简单,它对应vm里面的一个函数:
    void  CallFinalizer(Thread* FinalizerThread, Object* fobj)

      那么也就是
    fobjà edx
    FinalizerThread à ecx
    Call CallFinalizer
     
      但是,请注意!!!!!!!一个类包含析构函数和不包含析构函数,它对应的delete代码是不一样的,这点可以通过汇编代码比较得到,我这里就不多说了。
  • 相关阅读:
    切比雪夫不等式
    Myeclipse中可以正常显示,但运行后的网页找不到图片
    Hibernate Is Not Mapped(实体名 is not mapped [from book where id='0'])
    struts2-core-2.1.6.jar!/struts-default.xml无法加载的问题
    myeclipse 8.5最新注册码(过期时间到2016年)
    三大框架SSH(struts2+spring+hibernate)整合时相关配置文件的模板
    Initializing Spring root WebApplicationContext
    Unable to load bean: type: struts-derfault.xml异常
    Error creating bean with name 'bookDao'
    hibernate异常:Could not determine type for: java.util.Set
  • 原文地址:https://www.cnblogs.com/joeblackzqq/p/1959065.html
Copyright © 2011-2022 走看看