zoukankan      html  css  js  c++  java
  • (转)重识new

    layout: post
    title: 重识new
    categories: C/C++
    description: 重识new
    keywords:
    url: https://lichao890427.github.io/ https://github.com/lichao890427/

    一、new primer
      new操作符并不是c++的专属,c运行库有new.h头文件。C++中的new操作符,众所周知是用来分配动态内存的,而要能达到“动态”这种灵活性的特征,非堆区莫属,因为堆区支持手动分配。如果内存空间不够,new操作符返回空,或抛出异常(下面会讲述3种new操作符,常规new操作符、定位new操作符和禁止抛出异常的new操作符,如果禁止抛异常,那么只好返回空了)下面代码为MSDN中的,检测new分配是否成功,内存分配失败处理还可以通过_set_new_handler注册分配异常函数结果

    // insufficient_memory_conditions.cpp
    // compile with: /EHsc
    #include <iostream>
    using namespace std;
    #define BIG_NUMBER 100000000
    int main() {
    int *pI = new int[BIG_NUMBER];
    if( pI == 0x0 ) {
    cout << "Insufficient memory" << endl;
    return -1;
    }
    }
      对于对象使用new操作符的情况,生成的代码执行的流程一般为:先调用内部合适的new函数,该函数最终调用内存分配API进行堆内存分配,同时初始化一些方便内存管理的结构体和数据。成功分配后,将该地址视为this,如有必要则设置虚表指针,之后调用构造函数对该地址处对象进行构造。构造函数一般也是执行初始化功能而已,修改修改数据啥的。这些都是老生常谈不再赘述。
      即使请求的空间是0字节大小,new也会返回不同的地址,也就说总是在不同区域分配。这里注意和空类的区别,空类的大小是1,传给new函数(该函数在之后给出)之后,new函数接受到的参数只会>=1,而对于特殊情况:char* pch=new char[0];new函数接受的参数确实是0,不过分配的空间并不是0字节,因为存在内存管理相关的结构体也会占用内存。
      new操作符有2种作用域,一种是全局new,一种是类作用域new。用户在自定义类中可以重载自定义new函数。代码如下:

    #include <malloc.h>
    #include <memory.h>
    class Blanks
    {
    public:
    Blanks(){}
    void *operator new( size_t stAllocateBlock, char chInit );
    };
    void *Blanks::operator new( size_t stAllocateBlock, char chInit )
    {
    void *pvTemp = malloc( stAllocateBlock );
    if( pvTemp != 0 )
    memset( pvTemp, chInit, stAllocateBlock );
    return pvTemp;
    }
    // 对于Blanks对象,全局new操作符已被替换,因此下面的代码将分配sizeof(Blanks)大小的空间并把数据赋值为0xa5
    int main()
    {
    Blanks *a5 = new(0xa5) Blanks;
    return a5 != 0;
    }
    二、new primer plus
      new和delete操作符是通过一种称为自由存储区的内存池分配内存的,而new和delete操作符本身在编译时会由编译器选择合适的new函数和delete函数进行实现。C运行库的new函数会在失败时抛出std::bad_alloc异常,如果要使用不抛出异常的new版本,则需要链接nothrownew.obj,然而一旦链接了该文件,标准C++库中的new就不起作用了。new有2种语法形式

    [::] new [placement] new-type-name [new-initializer]
    [::] new [placement] ( type-name ) [new-initializer]
    2种new函数原型为:

    void* operator new( std::size_t _Count ) throw(bad_alloc);
    void* operator new( std::size_t _Count, const std::nothrow_t& ) throw( );
    void* operator new( std::size_t _Count, void* _Ptr ) throw( );
    void *operator new[]( std::size_t _Count ) throw(std::bad_alloc);
    void *operator new[]( std::size_t _Count, const std::nothrow_t& ) throw( );
    void *operator new[]( std::size_t _Count, void* _Ptr ) throw( );
    其用法如下:

    #include<new>
    #include<iostream>
    using namespace std;
    class MyClass
    {
    public:
    MyClass( )
    {
    cout << "Construction MyClass." << this << endl;
    };
    ~MyClass( )
    {
    imember = 0; cout << "Destructing MyClass." << this << endl;
    };
    int imember;
    };

    int main( )
    {
    // The first form of new delete
    MyClass* fPtr = new MyClass;
    delete fPtr;
    // The second form of new delete
    MyClass* fPtr2 = new( nothrow ) MyClass;
    delete fPtr2;
    // The third form of new delete
    char x[sizeof( MyClass )];
    MyClass* fPtr3 = new( &x[0] ) MyClass;
    fPtr3 -> ~MyClass();
    cout << "The address of x[0] is : " << ( void* )&x[0] << endl;
    }


    Construction MyClass.000B3F30
    Destructing MyClass.000B3F30
    Construction MyClass.000B3F30
    Destructing MyClass.000B3F30
    Construction MyClass.0023FC60
    Destructing MyClass.0023FC60
    The address of x[0] is : 0023FC60

    #include <new>
    #include <iostream>
    using namespace std;

    class MyClass {
    public:
    MyClass() {
    cout << "Construction MyClass." << this << endl;
    };

    ~MyClass() {
    imember = 0; cout << "Destructing MyClass." << this << endl;
    };
    int imember;
    };

    int main() {
    // The first form of new delete
    MyClass* fPtr = new MyClass[2];
    delete[ ] fPtr;
    // The second form of new delete
    char x[2 * sizeof( MyClass ) + sizeof(int)];
    MyClass* fPtr2 = new( &x[0] ) MyClass[2];
    fPtr2[1].~MyClass();
    fPtr2[0].~MyClass();
    cout << "The address of x[0] is : " << ( void* )&x[0] << endl;
    // The third form of new delete
    MyClass* fPtr3 = new( nothrow ) MyClass[2];
    delete[ ] fPtr3;
    }


    Construction MyClass.00311AEC
    Construction MyClass.00311AF0
    Destructing MyClass.00311AF0
    Destructing MyClass.00311AEC
    Construction MyClass.0012FED4
    Construction MyClass.0012FED8
    Destructing MyClass.0012FED8
    Destructing MyClass.0012FED4
    The address of x[0] is : 0012FED0
    Construction MyClass.00311AEC
    Construction MyClass.00311AF0
    Destructing MyClass.00311AF0
    Destructing MyClass.00311AEC
    探究第一种new形式实现
      第一种形式为常规new,MyClass* fPtr1 = new MyClass;

    // new.cpp
    void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
    { // try to allocate size bytes
    void *p;
    while ((p = malloc(size)) == 0)
    if (_callnewh(size) == 0)
    { // report no memory
    _THROW_NCEE(_XSTD bad_alloc, );
    }
    return (p);
    }
      从上面new函数实现可以看到是使用malloc函数进行分配,如果失败则调用_callnewh调用注册过的“new操作符失败回调”函数(注册用_set_new_handler),如果原先没注册new失败回调,则抛出bad_alloc异常,可见在默认情况下,该while只会执行1次,仅当自定义new失败回调函数返回true,才可能多次尝试分配。
      该种形式delete实现方式,单步以后vs不能定位到源码,不过我们可以换一种思路,既然知道一定执行析构函数,那么就在析构中下断点,断下后查看反汇编,并执行到上一级调用即可找到delete实现方法,因此看汇编实现,发现是一个名为“scalar deleting destructor”的内部函数:

    //
    00EB3470 push ebp
    00EB3471 mov ebp,esp
    00EB3473 sub esp,0CCh
    00EB3479 push ebx
    00EB347A push esi
    00EB347B push edi
    00EB347C push ecx
    00EB347D lea edi,[ebp-0CCh]
    00EB3483 mov ecx,33h
    00EB3488 mov eax,0CCCCCCCCh
    00EB348D rep stos dword ptr es:[edi]
    00EB348F pop ecx //以上部分为debug版API常见头,无需理会
    00EB3490 mov dword ptr [this],ecx
    00EB3493 mov ecx,dword ptr [this]
    00EB3496 call MyClass::~MyClass (0EB1023h) //执行析构
    00EB349B mov eax,dword ptr [ebp+8]
    00EB349E and eax,1
    00EB34A1 je MyClass::`scalar deleting destructor'+3Fh (0EB34AFh) //如果传入参数允许释放则进行调用对应delete函数(对于定位new对应的delete该参数是设置为不允许的)
    00EB34A3 mov eax,dword ptr [this]
    00EB34A6 push eax
    00EB34A7 call operator delete (0EB1154h)
    00EB34AC add esp,4
    00EB34AF mov eax,dword ptr [this] //以下是无关的收尾工作
    00EB34B2 pop edi
    00EB34B3 pop esi
    00EB34B4 pop ebx
    00EB34B5 add esp,0CCh
    00EB34BB cmp ebp,esp
    00EB34BD call __RTC_CheckEsp (0EB1352h)
    00EB34C2 mov esp,ebp
    00EB34C4 pop ebp
    00EB34C5 ret 4
      执行到call operator delete这行,步入之后转到源码,可以看到使用的是dbgdel.cpp的delete函数。实现如下

    // dbgdel.cpp
    void operator delete( void *pUserData )
    {
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
    return;
    _mlock(_HEAP_LOCK); /* 阻塞其他线程*/
    __TRY
    /* 得到用于内存块信息头指针*/
    pHead = pHdr(pUserData);
    /* 检查区块类型 */
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    _free_dbg( pUserData, pHead->nBlockUse );//调用free函数释放内存
    __FINALLY
    _munlock(_HEAP_LOCK); /* 解锁其他线程*/
    __END_TRY_FINALLY
    return;
    }
    探究第二种new形式实现
      第二种方式为不抛出异常的new,MyClass* fPtr2 = new( nothrow ) MyClass;,可见,这里用try块捕获了异常,因此不再抛出异常,余下的就是调用常规new函数而已。

    // newopnt.cpp
    void * __CRTDECL operator new(size_t count, const std::nothrow_t&) _THROW0()
    { // try to allocate count bytes
    void *p;
    _TRY_BEGIN
    p = operator new(count);
    _CATCH_ALL
    p = 0;
    _CATCH_END
    return (p);
    }
    #define _TRY_BEGIN try {
    #define _CATCH(x) } catch (x) {
    #define _CATCH_ALL } catch (...) {
    #define _CATCH_END }
    探究第三种new形式实现
      第三种方式为布局new:
    char x1[sizeof( MyClass )];MyClass* fPtr3 = new( &x1[0] ) MyClass;
      这里所谓的布局就是说告诉new我们已经有一个内存位置了:

    // new
    inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
    { // construct array with placement at _Where
    return (_Where);
    }
      可以发现该new什么都没做,那么为什么还要new呢?仔细想想可以知道,编译器对new的处理是调用new函数后,之后将该地址作为this指针进行初始化操作(比如设置虚表),再调用构造函数,而构造函数这玩意不能直接调用,不像析构函数那样,因为构造之前还没有对象和指针呢,对象和指针是构造以后才有的,而调用析构函数的时候,是已经有对象或指针的。所以这种定位new在我理解,就是可以相当于可以直接构造了。
      从上面可以看到new操作符先调用合适的new函数分配空间,之后调用构造函数构造,而delete函数刚好相对,先进行析构之后调用析构函数析构;同时可以看到布局new操作符的好处是可以手动指定构造和析构的时间,对于new无论哪种形式,在调用new函数分配好内存后都会调用构造函数进行构造,而定位new函数实则是直接返回,这就导致直接使用当前地址进行构造,相当于显示调用构造函数,而析构时由于没有实际分配空间,因此不能用delete,而是显示调用析构函数进行析构。
      上面都是对于有构造函数和析构函数对象的情况,用delete时,编译器会为该类专门生成一个scalar deleting destructor函数,该函数中先进行析构,之后调用operator delete函数。当然,如果没有析构函数,那么就不会有scalar deleting destructor函数了,此时单步是可以看到delete源码的,即dbgdel.cpp中的void operator delete(void *pUserData)函数。这一点在delete用于基本类型时显而易见。

    探究第一种new[]形式实现
      第一种类型new,MyClass* fPtr4 = new MyClass[2]

    // newaop.cpp
    void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
    { // try to allocate count bytes for an array
    return (operator new(count));
    }
      而编译器传给该new[]函数的参数count是sizeof(MyClass[2])+sizeof(int),该sizeof(int)用于内存管理。new仍然调用了new();没有本质区别,即这么多对象占用的内存是当作整体分配的。再分配好之后,就需要对每个对象this指针处进行初始化和构造了。
      通过逆向分析可知先调用了new[],如果成功分配内存,则调用数组构造迭代器vector_constructor_iterator对每个对象进行构造。void* base=new[](sizeof(int)+sizeof(MyClass[2]));,起始4字节存储要初始化的对象个数,剩余空间为对象占用内存

    push 0Ch ; count
    call j_??_U@YAPAXI@Z ; operator new[](uint)
    add esp, 4
    mov [ebp+var_1A0], eax
    mov [ebp+var_4], 3
    cmp [ebp+var_1A0], 0
    jz short loc_41851B
    mov eax, [ebp+var_1A0]
    mov dword ptr [eax], 2
    push offset j_??1MyClass@@QAE@XZ ; pDtor
    push offset j_??0MyClass@@QAE@XZ ; pCtor
    push 2 ; count
    push 4 ; size
    mov ecx, [ebp+var_1A0]
    add ecx, 4
    push ecx ; ptr
    call j_??_L@YGXPAXIHP6EX0@Z1@Z ; `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))
    ; ---------------------------------------------------------------------------
    mov edx, [ebp+var_1A0]
    add edx, 4
    mov [ebp+var_22C], edx
    jmp short loc_418525
    ; ---------------------------------------------------------------------------

    loc_41851B: ; CODE XREF: _main+1FF j
    mov [ebp+var_22C], 0

    loc_418525: ; CODE XREF: _main+239 j
    mov eax, [ebp+var_22C]
    mov [ebp+var_1AC], eax
    mov [ebp+var_4], 0FFFFFFFFh
    mov ecx, [ebp+var_1AC]
    mov [ebp+fPtr4], ecx
    if(base)
    {
    *(int*)base=2;//2个对象
    vector_construtor_iterator((MyClass*)((char*)base+4),sizeof(MyClass[2]),2,&MyClass::MyClass,&MyClass::~MyClass);
    }
    vector_constructor_iterator对应代码为:

    ; void __stdcall `eh vector constructor iterator'(void *ptr, unsigned int size, int count, void (__thiscall *pCtor)(void *), void (__thiscall *pDtor)(void *))
    ??_L@YGXPAXIHP6EX0@Z1@Z proc near ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)) j

    success = dword ptr -20h
    i = dword ptr -1Ch
    ms_exc = CPPEH_RECORD ptr -18h
    ptr = dword ptr 8
    size = dword ptr 0Ch
    count = dword ptr 10h
    pCtor = dword ptr 14h
    pDtor = dword ptr 18h

    push ebp
    mov ebp, esp
    push 0FFFFFFFEh
    push offset stru_41F9A0
    push offset j___except_handler4
    mov eax, large fs:0
    push eax
    add esp, 0FFFFFFF0h
    push ebx
    push esi
    push edi
    mov eax, ___security_cookie
    xor [ebp+ms_exc.registration.ScopeTable], eax
    xor eax, ebp
    push eax
    lea eax, [ebp+ms_exc.registration]
    mov large fs:0, eax
    mov [ebp+success], 0
    mov [ebp+ms_exc.registration.TryLevel], 0
    mov [ebp+i], 0
    jmp short loc_415730
    ; ---------------------------------------------------------------------------

    loc_415727: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+67 j
    mov eax, [ebp+i]
    add eax, 1
    mov [ebp+i], eax

    loc_415730: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+45 j
    mov ecx, [ebp+i]
    cmp ecx, [ebp+count]
    jge short loc_415749
    mov ecx, [ebp+ptr]
    call [ebp+pCtor]
    mov edx, [ebp+ptr]
    add edx, [ebp+size]
    mov [ebp+ptr], edx
    jmp short loc_415727
    ; ---------------------------------------------------------------------------

    loc_415749: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+56 j
    mov [ebp+success], 1
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
    call $LN9 ; Finally handler 0 for function 4156E0
    ; ---------------------------------------------------------------------------

    loc_41575C: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)):$LN10 j
    jmp short $LN12
    ; ---------------------------------------------------------------------------

    $LN9: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+77 j
    ; DATA XREF: .rdata:stru_41F9A0 o
    cmp [ebp+success], 0 ; Finally handler 0 for function 4156E0
    jnz short $LN10
    mov eax, [ebp+pDtor]
    push eax ; pDtor
    mov ecx, [ebp+i]
    push ecx ; count
    mov edx, [ebp+size]
    push edx ; size
    mov eax, [ebp+ptr]
    push eax ; ptr
    call j_?__ArrayUnwind@@YGXPAXIHP6EX0@Z@Z ; __ArrayUnwind(void *,uint,int,void (*)(void *))

    $LN10: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+82 j
    retn
    ; ---------------------------------------------------------------------------

    $LN12: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)):loc_41575C j
    mov ecx, [ebp+ms_exc.registration.Next]
    mov large fs:0, ecx
    pop ecx
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    retn 14h
    ??_L@YGXPAXIHP6EX0@Z1@Z endp

    逆向分析得到C++语法:

    void __stdcall vector_constructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pCtor)(void *), void (__thiscall *pDtor)(void *))
    {
    int i=0;
    __try
    {
    for(;i<count;i++,objs++)
    {
    objs->pCtor();//用构造函数构造
    }
    }
    __except(1)
    {
    __ArrayUnwind(objs,size,i,pDtor);//如果某个构造函数产生异常,则进行栈解退,用到析构函数
    }
    }
      栈解退,这里引用C++ Primer Plus的解释:“现在假设函数由于出现异常而终止(而不是由于返回),则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址,随后控制权将转到块尾的异常处理程序,而不会函数调用后面的第一条语句。这个过程称为栈解退,引发机制的一个非常重要的特性是,和函数返回一样,对于栈中的自动类对象,而throw语句则处理try块和throw之间的整个函数调用徐丽放在栈中的对象。如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的自动类对象,其析构函数将不会被调用。”。unwind就是解退的意思,现在来查看__ArrayUnwind的源码,根据函数名可知该函数用于对象数组解退:


    push ebp
    mov ebp, esp
    push 0FFFFFFFEh
    push offset stru_41F9E0
    push offset j___except_handler4
    mov eax, large fs:0
    push eax
    sub esp, 8
    push ebx
    push esi
    push edi
    mov eax, ___security_cookie
    xor [ebp+ms_exc.registration.ScopeTable], eax
    xor eax, ebp
    push eax
    lea eax, [ebp+ms_exc.registration]
    mov large fs:0, eax
    mov [ebp+ms_exc.old_esp], esp
    mov [ebp+ms_exc.registration.TryLevel], 0

    loc_41591A: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+54 j
    mov eax, [ebp+count]
    sub eax, 1
    mov [ebp+count], eax
    js short loc_415936
    mov ecx, [ebp+ptr]
    sub ecx, [ebp+size]
    mov [ebp+ptr], ecx
    mov ecx, [ebp+ptr]
    call [ebp+pDtor]
    jmp short loc_41591A
    ; ---------------------------------------------------------------------------

    loc_415936: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+43 j
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
    jmp short loc_415956
    ; ---------------------------------------------------------------------------

    $LN7: ; DATA XREF: .rdata:stru_41F9E0 o
    mov edx, [ebp+ms_exc.exc_ptr] ; Exception filter 0 for function 4158E0
    push edx ; pExPtrs
    call ArrayUnwindFilter
    add esp, 4

    $LN9_1:
    retn
    ; ---------------------------------------------------------------------------

    $LN8_0: ; DATA XREF: .rdata:stru_41F9E0 o
    mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 4158E0
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh

    loc_415956: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+5D j
    mov ecx, [ebp+ms_exc.registration.Next]
    mov large fs:0, ecx
    pop ecx
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    retn 10h

    逆向分析得到C++代码:

    void __stdcall __ArrayUnwind(MyClass* objs,unsigned size,int count,void (__thiscall *pDtor)(void*))
    {//第[count]对象由于没有构造成功,因此从[count-1]个对象开始析构
    __try
    {
    while(count--)
    {
    objs--;
    objs->pDtor();
    }
    }
    __except(terminate(),0)//如果析构发生异常,则终止程序
    {
    return;
    }
    }
    探究第一种形式delete[]形式实现
    mov eax, [ebp+fPtr4]
    mov [ebp+var_188], eax
    mov ecx, [ebp+var_188]
    mov [ebp+var_194], ecx
    cmp [ebp+var_194], 0
    jz short loc_418574//如果之前new成功,则往下执行
    push 3 ; unsigned int
    mov ecx, [ebp+var_194] ; this
    call j_??_EMyClass@@QAEPAXI@Z ; MyClass::`vector deleting destructor'(uint)
    mov [ebp+var_22C], eax
    jmp short loc_41857E
    ; ---------------------------------------------------------------------------

    loc_418574: ; CODE XREF: _main+27D j
    mov [ebp+var_22C], 0

      可见vector_deleting_destructor是用来析构对象数组的,原型为void* __thiscall MyClass::vector_deleting_destructor(usigned int flag);,该函数是编译器内部为MyClass类加的成员函数
    flag含义未知,所以需要分析该函数源码:

    push ebp
    mov ebp, esp
    sub esp, 0CCh
    push ebx
    push esi
    push edi
    push ecx
    lea edi, [ebp+var_CC]
    mov ecx, 33h
    mov eax, 0CCCCCCCCh
    rep stosd
    pop ecx
    mov [ebp+this], ecx
    mov eax, [ebp+arg_0]
    and eax, 2
    jz short loc_413431
    push offset j_??1MyClass@@QAE@XZ ; pDtor
    mov eax, [ebp+this]
    mov ecx, [eax-4]
    push ecx ; count
    push 4 ; size
    mov edx, [ebp+this]
    push edx ; ptr
    call j_??_M@YGXPAXIHP6EX0@Z@Z ; `eh vector destructor iterator'(void *,uint,int,void (*)(void *))
    ; ---------------------------------------------------------------------------
    mov eax, [ebp+arg_0]
    and eax, 1
    jz short loc_413429
    mov eax, [ebp+this]
    sub eax, 4
    push eax ; void *
    call j_??_V@YAXPAX@Z_0 ; operator delete[](void *)
    add esp, 4

    loc_413429: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+48 j
    mov eax, [ebp+this]
    sub eax, 4
    jmp short loc_413450
    ; ---------------------------------------------------------------------------

    loc_413431: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+29 j
    mov ecx, [ebp+this] ; this
    call j_??1MyClass@@QAE@XZ ; MyClass::~MyClass(void)
    mov eax, [ebp+arg_0]
    and eax, 1
    jz short loc_41344D
    mov eax, [ebp+this]
    push eax ; void *
    call j_??3@YAXPAX@Z_0 ; operator delete(void *)
    add esp, 4

    loc_41344D: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+6F j
    mov eax, [ebp+this]

    loc_413450: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+5F j
    pop edi
    pop esi
    pop ebx
    add esp, 0CCh
    cmp ebp, esp
    call j___RTC_CheckEsp
    mov esp, ebp
    pop ebp
    retn 4

    经过逆向分析得到C++代码:

    void* __thiscall MyClass::vector_deleting_destructor(usigned int flag)
    {
    if(flag&2)//由于push的是3,因此这里成立
    {
    vector_destructor_iterator(this,sizeof(MyClass),*(int*)((char*)this-4),MyClass::~MyClass);
    if(flag&1))//由于push的是3,因此这里成立
    {
    delete[]((char*)this-4);
    }
    }
    else
    {
    this->~MyClass();
    if(flag&1)
    {
    delete(this);
    }
    }
    }
    仅从以上代码可以分析出以下几点:

    1.this-4这个地址为之前new成功分配所返回值,可以将其看成sizeof(int)+sizeof(MyClass[2])大小的结构体,第一个成员为对象个数。
    2.该函数对数组和非数组进行了分别处理,可以分析出第2个二进制位为1时,是析构对象数组,为0时是析构普通对象。而第1个二进制位是规定是否释放内存,可以想象如果这里是定位new,那么这里是不应该释放的。
    3.vector_destructor_iterator起实际析构作用原型void __stdcall vector_destructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pDtor)(void *));,下面来看该函数
    push ebp
    mov ebp, esp
    push 0FFFFFFFEh
    push offset stru_41F9C0
    push offset j___except_handler4
    mov eax, large fs:0
    push eax
    add esp, 0FFFFFFF4h
    push ebx
    push esi
    push edi
    mov eax, ___security_cookie
    xor [ebp+ms_exc.registration.ScopeTable], eax
    xor eax, ebp
    push eax
    lea eax, [ebp+ms_exc.registration]
    mov large fs:0, eax
    mov [ebp+success], 0
    mov eax, [ebp+size]
    imul eax, [ebp+count]
    add eax, [ebp+ptr]
    mov [ebp+ptr], eax
    mov [ebp+ms_exc.registration.TryLevel], 0

    loc_41580B: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+65 j
    mov ecx, [ebp+count]
    sub ecx, 1
    mov [ebp+count], ecx
    js short loc_415827
    mov edx, [ebp+ptr]
    sub edx, [ebp+size]
    mov [ebp+ptr], edx
    mov ecx, [ebp+ptr]
    call [ebp+pDtor]
    jmp short loc_41580B
    ; ---------------------------------------------------------------------------

    loc_415827: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+54 j
    mov [ebp+success], 1
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
    call $LN8 ; Finally handler 0 for function 4157C0
    ; ---------------------------------------------------------------------------

    loc_41583A: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *)):$LN9_0 j
    jmp short $LN11
    ; ---------------------------------------------------------------------------

    $LN8: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+75 j
    ; DATA XREF: .rdata:stru_41F9C0 o
    cmp [ebp+success], 0 ; Finally handler 0 for function 4157C0
    jnz short $LN9_0
    mov eax, [ebp+pDtor]
    push eax ; pDtor
    mov ecx, [ebp+count]
    push ecx ; count
    mov edx, [ebp+size]
    push edx ; size
    mov eax, [ebp+ptr]
    push eax ; ptr
    call j_?__ArrayUnwind@@YGXPAXIHP6EX0@Z@Z ; __ArrayUnwind(void *,uint,int,void (*)(void *))

    $LN9_0: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+80 j
    retn
    ; ---------------------------------------------------------------------------

    $LN11: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *)):loc_41583A j
    mov ecx, [ebp+ms_exc.registration.Next]
    mov large fs:0, ecx
    pop ecx
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    retn 10h

    经过逆向分析得到C++代码:

    void __stdcall vector_destructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pDtor)(void *))
    {
    int i=0;
    __try
    {
    MyClass* last=objs+size-1;//从最后一个对象开始析构
    while(count--)
    {
    last->pDtor();
    last--;
    }
    }
    __except(1)
    {
    __ArrayUnwind(objs,size,count,pDtor);//如果某个析构函数产生异常,则跳过该对象,继续析构之前的对象
    }
    }
      鉴于__ArrayUnwind前面已经介绍过,这里就不分析了。如果仔细分析上一节开头给出main汇编代码,会发现只要new成功了,delete都会去执行析构,即使出现对象数组中某个对象构造失败导致已经进行析构,delete时所有元素仍会析构一次。

    探究第二种情况new[]形式实现
    MyClass* fPtr5 = new( nothrow ) MyClass[2];

    // newaopnt.cpp
    void * __CRTDECL operator new[](::size_t count, const std::nothrow_t& x)
    _THROW0()
    { // try to allocate count bytes for an array
    return (operator new(count, x));
    }
    可见调用了单对象的第二种new形式,与非数组形式类似,不再赘述

    探究第三种情况new[]形式实现
    char x2[2*sizeof( MyClass ) + sizeof(int)];
    MyClass* fPtr6 = new ( &x2[0] ) MyClass[2];

    inline void *__CRTDECL operator new[](size_t, void *_Where) _THROW0()
    { // construct array with placement at _Where
    return (_Where);
    }
      可见等同于对象第三种new形式,不再赘述。下面我们来看看其他内存分配函数

    malloca
    void* _malloca(size_t size);
      MSDN里是这么描述的:在栈上分配内存,是_alloca的安全性增强版本。返回指针是根据对象大小对齐,如果size是0则返回长度0的合法指针。如果地址空间无法分配会抛出一个栈溢出异常,该异常不是C++异常,需要使用SEH。_malloca和_alloca的区别在于_alloc无论大小总是在栈上分配,且无需free释放内存。而_malloca需要使用_freea释放内存,在调试模式下,_malloca总是在堆上分配。在异常处理时显式调用_malloca有一些限制,x86架构处理器异常处理例程会自动控制函数栈帧,在执行操作时并不基于当前闭合函数栈帧,这一点在Windows NT SEH和C++异常处理的catch语句中很常见。因此在以下情况显示调用_malloca,在执行异常处理例程后会产生程序崩溃。
      Windows NT SEH异常过滤表达式:__except(_malloca())
      Windows NT SEH最终执行表达式:__finally(_malloca())
      C++ 异常处理 catch语句
      然而_malloca可以从异常处理例程中除上述情况以外的情况下直接调用,或在异常处理所触发的回调函数中调用也是允许的。先来看一个例子:

    #include <windows.h>
    #include <stdio.h>
    #include <malloc.h>

    int main()
    {
    int size;
    int numberRead = 0;
    int errcode = 0;
    void *p = NULL;
    void *pMarker = NULL;

    while (numberRead == 0)
    {
    printf_s("Enter the number of bytes to allocate "
    "using _malloca: ");
    numberRead = scanf_s("%d", &size);
    }

    // Do not use try/catch for _malloca,
    // use __try/__except, since _malloca throws
    // Structured Exceptions, not C++ exceptions.

    __try
    {
    if (size > 0)
    {
    p = _malloca( size );
    }
    else
    {
    printf_s("Size must be a positive number.");
    }
    _freea( p );
    }

    // Catch any exceptions that may occur.
    __except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
    {
    printf_s("_malloca failed! ");

    // If the stack overflows, use this function to restore.
    errcode = _resetstkoflw();
    if (errcode)
    {
    printf("Could not reset the stack!");
    _exit(1);
    }
    };
    }
    // malloc.h
    #define _ALLOCA_S_THRESHOLD 1024
    #define _ALLOCA_S_STACK_MARKER 0xCCCC
    #define _ALLOCA_S_HEAP_MARKER 0xDDDD

    #if defined(_M_IX86)
    #define _ALLOCA_S_MARKER_SIZE 8
    #elif defined(_M_X64)
    #define _ALLOCA_S_MARKER_SIZE 16
    #elif defined(_M_ARM)
    #define _ALLOCA_S_MARKER_SIZE 8
    #elif !defined (RC_INVOKED)
    #error Unsupported target platform.
    #endif

    ......

    #if !defined(__midl) && !defined(RC_INVOKED)
    #pragma warning(push)
    #pragma warning(disable:6540)
    __inline void *_MarkAllocaS(_Out_opt_ __crt_typefix(unsigned int*) void *_Ptr, unsigned int _Marker)
    {
    if (_Ptr)
    {
    *((unsigned int*)_Ptr) = _Marker;
    _Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
    }
    return _Ptr;
    }
    #pragma warning(pop)
    #endif

    #if defined(_DEBUG)
    #if !defined(_CRTDBG_MAP_ALLOC)
    #undef _malloca
    #define _malloca(size)
    __pragma(warning(suppress: 6255))
    _MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER)
    #endif
    #else
    #undef _malloca
    #define _malloca(size)
    __pragma(warning(suppress: 6255))
    ((((size) + _ALLOCA_S_MARKER_SIZE) <= _ALLOCA_S_THRESHOLD) ?
    _MarkAllocaS(_alloca((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_STACK_MARKER) :
    _MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER))
    #endif
      可以分析出DEBUG版下,宏调用了malloc进行分配,之后使用_MarkAllocaS对分配内存进行一些处理(后面讨论),而RELEASE版下,宏先判断要分配的内存是否过大,该门限为_ALLOCA_S_THRESHOLD-_ALLOCA_S_MARKER_SIZE=1016,如果超过该值则调用malloc,否则调用_alloca。从字面意思上可以知道_ALLOCA_S_HEAP_MARKER这个标志位说明该内存区是在堆上分配的,而_ALLOCA_S_STACK_MARKER标志是在栈上分配的。在malloc或_alloca分配成功后,总会调用_MarkAllocaS进行调整。结合字面意思和5行C语言代码可知,在执行过内存分配后,返回的指针前sizeof(unigned int*)字节为分配内存类型标志,之后指针调整到空闲位置丢给用户操作。那么所有的问题都落在_alloca和malloc的源码上,下面会进行分析。
      _alloca(我第一次见栈上分配内存是在逆向一个易语言程序时,用的是sub esp,而微软这个函数是第一次见)void* _alloca(size_t size);,该函数只在程序栈中分配字节,而函数退出时该空间会自动释放,因此无需手动释放。用此函数的限制和_malloca相同。

    #include <windows.h>
    #include <stdio.h>
    #include <malloc.h>

    int main()
    {
    int size = 1000;
    int errcode = 0;
    void *pData = NULL;

    // 注意:不要使用try/catch,而要使用__try/__except,因为_alloca抛出SEH而不是C++异常
    __try {
    // 使用_alloca分配太大的空间很容易崩溃,推荐1024字节以下的空间
    if (size > 0 && size < 1024)
    {
    pData = _alloca( size );
    printf_s( "Allocated %d bytes of stack at 0x%p",
    size, pData);
    }
    else
    {
    printf_s("Tried to allocate too many bytes. ");
    }
    }

    // 如果溢出
    __except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
    {
    printf_s("_alloca failed! ");

    // 使用下面的函数恢复函数栈
    errcode = _resetstkoflw();
    if (errcode)
    {
    printf_s("Could not reset the stack! ");
    _exit(1);
    }
    };
    }
    来看反汇编

    ; int __cdecl main()
    _main proc near ; CODE XREF: j__main j

    pAllocaBase = dword ptr -120h
    cbSize = dword ptr -11Ch
    var_114 = dword ptr -114h
    allocaList = dword ptr -48h
    pData = dword ptr -3Ch
    errcode = dword ptr -30h
    size = dword ptr -24h
    var_1C = dword ptr -1Ch
    ms_exc = CPPEH_RECORD ptr -18h

    push ebp
    mov ebp, esp
    push 0FFFFFFFEh
    push offset stru_416F80
    push offset j___except_handler4
    mov eax, large fs:0
    push eax
    add esp, 0FFFFFEF0h
    push ebx
    push esi
    push edi
    lea edi, [ebp+pAllocaBase]
    mov ecx, 42h
    mov eax, 0CCCCCCCCh
    rep stosd
    mov eax, ___security_cookie
    xor [ebp+ms_exc.registration.ScopeTable], eax
    xor eax, ebp
    mov [ebp+var_1C], eax
    push eax
    lea eax, [ebp+ms_exc.registration]
    mov large fs:0, eax
    mov [ebp+ms_exc.old_esp], esp
    mov [ebp+allocaList], 0
    mov [ebp+size], 3E8h
    mov [ebp+errcode], 0
    mov [ebp+pData], 0
    mov [ebp+ms_exc.registration.TryLevel], 0
    cmp [ebp+size], 0
    jle short loc_4114D3
    cmp [ebp+size], 400h
    jge short loc_4114D3
    mov eax, [ebp+size]
    add eax, 24h
    mov [ebp+cbSize], eax
    mov eax, [ebp+cbSize]
    call j___alloca_probe_16
    mov [ebp+pAllocaBase], esp
    mov [ebp+ms_exc.old_esp], esp
    lea ecx, [ebp+allocaList]
    push ecx ; pAllocaInfoList
    mov edx, [ebp+cbSize] ; cbSize
    mov ecx, [ebp+pAllocaBase] ; pAllocaBase
    call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
    add [ebp+pAllocaBase], 20h
    mov edx, [ebp+pAllocaBase]
    mov [ebp+pData], edx
    mov esi, esp
    mov eax, [ebp+pData]
    push eax
    mov ecx, [ebp+size]
    push ecx
    push offset Format ; "Allocated %d bytes of stack at 0x%p"
    call ds:__imp__printf_s
    add esp, 0Ch
    cmp esi, esp
    call j___RTC_CheckEsp
    jmp short loc_4114EA
    ; ---------------------------------------------------------------------------

    loc_4114D3: ; CODE XREF: _main+72 j
    ; _main+7B j
    mov esi, esp
    push offset aTriedToAllocat ; "Tried to allocate too many bytes. "
    call ds:__imp__printf_s
    add esp, 4
    cmp esi, esp
    call j___RTC_CheckEsp

    loc_4114EA: ; CODE XREF: _main+E1 j
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
    jmp loc_41158D
    ; ---------------------------------------------------------------------------

    $LN10: ; DATA XREF: .rdata:stru_416F80 o
    mov eax, [ebp+ms_exc.exc_ptr] ; Exception filter 0 for function 4113F0
    mov ecx, [eax]
    mov edx, [ecx]
    mov [ebp+var_114], edx
    cmp [ebp+var_114], 0C00000FDh
    jnz short loc_41151B
    mov [ebp+cbSize], 1
    jmp short loc_411525
    ; ---------------------------------------------------------------------------

    loc_41151B: ; CODE XREF: _main+11D j
    mov [ebp+cbSize], 0

    loc_411525: ; CODE XREF: _main+129 j
    mov eax, [ebp+cbSize]

    $LN12:
    retn
    ; ---------------------------------------------------------------------------

    $LN11: ; DATA XREF: .rdata:stru_416F80 o
    mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 4113F0
    mov esi, esp
    push offset a_allocaFailed ; "_alloca failed! "
    call ds:__imp__printf_s
    add esp, 4
    cmp esi, esp
    call j___RTC_CheckEsp
    mov esi, esp
    call ds:__imp___resetstkoflw
    cmp esi, esp
    call j___RTC_CheckEsp
    mov [ebp+errcode], eax
    cmp [ebp+errcode], 0
    jz short loc_411586
    mov esi, esp
    push offset aCouldNotResetT ; "Could not reset the stack! "
    call ds:__imp__printf_s
    add esp, 4
    cmp esi, esp
    call j___RTC_CheckEsp
    mov esi, esp
    push 1 ; Code
    call ds:__imp___exit
    ; ---------------------------------------------------------------------------
    cmp esi, esp
    call j___RTC_CheckEsp

    loc_411586: ; CODE XREF: _main+16C j
    mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh

    loc_41158D: ; CODE XREF: _main+101 j
    jmp short loc_411591
    ; ---------------------------------------------------------------------------
    jmp short loc_411593
    ; ---------------------------------------------------------------------------

    loc_411591: ; CODE XREF: _main:loc_41158D j
    xor eax, eax

    loc_411593: ; CODE XREF: _main+19F j
    push edx
    mov ecx, ebp ; frame
    push eax
    lea edx, v ; v
    push [ebp+allocaList] ; allocaList
    call j_@_RTC_CheckStackVars2@12 ; _RTC_CheckStackVars2(x,x,x)
    pop eax
    pop edx
    lea esp, [ebp-130h]
    mov ecx, [ebp+ms_exc.registration.Next]
    mov large fs:0, ecx
    pop ecx
    pop edi
    pop esi
    pop ebx
    mov ecx, [ebp+var_1C]
    xor ecx, ebp ; cookie
    call j_@__security_check_cookie@4 ; __security_check_cookie(x)
    mov esp, ebp
    pop ebp
    retn

    mov eax, [ebp+size]
    add eax, 24h
    mov [ebp+cbSize], eax
    mov eax, [ebp+cbSize]
    call j___alloca_probe_16
    mov [ebp+pAllocaBase], esp
    mov [ebp+ms_exc.old_esp], esp
    lea ecx, [ebp+allocaList]
    push ecx ; pAllocaInfoList
    mov edx, [ebp+cbSize] ; cbSize
    mov ecx, [ebp+pAllocaBase] ; pAllocaBase
    call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
    add [ebp+pAllocaBase], 20h
    mov edx, [ebp+pAllocaBase]
    mov [ebp+pData], edx

      很疑惑地,在__alloca_probe_16调用之前,发生了add eax,24h和mov eax,[ebp+cbSize],而在之后发生了mov [ebp+pAllocaBase], esp,那么大胆做出猜测:

    1.add eax,24h,说明这24h字节用来实现内存管理或字节对齐之类功能
    2.__alloca_probe_16为接受一个参数的函数,该参数通过eax传递,进行的操作是修改esp,因此esp可以看做执行结果
    3.另一个函数原型为void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList),反汇编得到
    ; void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
    @_RTC_AllocaHelper@12 proc near ; CODE XREF: _RTC_AllocaHelper(x,x,x) j
    pAllocaInfoList = dword ptr 8
    pAllocaBase = ecx
    cbSize = edx
    push ebp
    mov ebp, esp
    push ebx
    push esi
    mov esi, pAllocaBase
    mov ebx, cbSize
    test esi, esi
    jz short loc_4116CC
    test ebx, ebx
    jz short loc_4116CC
    mov cbSize, [ebp+pAllocaInfoList]
    test cbSize, cbSize
    jz short loc_4116CC
    push edi
    mov al, 0CCh
    mov edi, esi
    mov pAllocaBase, ebx
    rep stosb
    mov eax, [cbSize]
    mov [esi+4], eax
    mov [esi+0Ch], ebx
    mov [cbSize], esi
    pop edi
    loc_4116CC: ; CODE XREF: _RTC_AllocaHelper(x,x,x)+B j
    ; _RTC_AllocaHelper(x,x,x)+F j ...
    pop esi
    pop ebx
    pop ebp
    retn 4
    @_RTC_AllocaHelper@12 endp
    逆向分析得道C++代码:

    void main()
    {
    ......
    int size=1024,cbsize=size+sizeof(_RTC_ALLOCA_NODE)+4;
    _RTC_ALLOCA_NODE* pAllocaBase=__alloca_probe_16(cbsize);
    _RTC_AllocaHelper(pAllocaBase,cbsize,NULL);
    void* pData=(void*)(pAllocaBase+1);
    ......
    }

    //这个结构体来自于reactos
    #pragma pack(push,1)
    typedef struct _RTC_ALLOCA_NODE
    {
    __int32 guard1;
    struct _RTC_ALLOCA_NODE *next;
    #if (defined(_X86_) && !defined(__x86_64))
    __int32 dummypad;
    #endif
    size_t allocaSize;
    #if (defined(_X86_) && !defined(__x86_64))
    __int32 dummypad2;
    #endif
    __int32 guard2[3];
    }_RTC_ALLOCA_NODE;
    #pragma pack(pop)

    void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
    {//初始化已分配空间,可以用于维护调试版函数栈
    if(pAllocaBase && cbSize && pAllocaInfoList)//由于最后一个参数在本例中为0,这个函数实际相当于没有执行
    {
    memset(pAllocaBase,0xCC,cbSize);//经常在调试版程序栈空间看到0xCC "烫烫烫烫烫" 对吧,就是这样的。。。
    pAllocaBase->next=*pAllocaInfoList;//链接到前一个结构;
    pAllocaBase->allocaSize=cbSize;
    *pAllocaInfoList=pAllocaBase;//自此可知,上述结构形成链表,pAllocaInfoList指向当前结构
    }
    }
    //__alloca_probe_16代码下面会进行分析
      以上是debug版的情况,如果尝试用release版查看反汇编代码,会发现只有push和call __alloca_probe_16部分,可知add eax,24和AllocaHelper只是调试版本用于内存管理的。所以重点落在该函数的解析上。进入源代码查看,__alloca_probe_16用来按16字节对齐内存,而chkstk子例程进行实际分配操作:

    // alloca16.asm

    ; _alloca_probe_16, _alloca_probe_8 - 按8/16字节对齐例程
    ;输入:EAX = 栈帧大小
    ;输出:调整EAX,修改esp.

    public _alloca_probe_8
    _alloca_probe_16 proc ; 16 byte aligned alloca

    push ecx
    lea ecx, [esp] + 8 ; 父函数栈顶(call _alloca_probe_16和push ecx)
    sub ecx, eax ;
    and ecx, (16 - 1) ; 计算地址低4位未对齐偏移
    add eax, ecx ; 增加cbSize使其对齐
    sbb ecx, ecx ; 如果cbSize溢出,ecx = 0xFFFFFFFF,否则ecx = 0
    or eax, ecx ; 如果溢出,则eax = 0xFFFFFFFF
    pop ecx ; 还原ecx
    jmp _chkstk ; eax存储修正cbSize,并交给_chkstk处理
    _alloca_probe_16 endp

    end


    public _alloca_probe
    _chkstk proc
    _alloca_probe = _chkstk
    push ecx
    lea ecx, [esp] + 8 - 4 ; 考虑到之后的ret指令对未来esp的修改
    sub ecx, eax ; 分配栈空间,ecx存储更新后的栈位置
    sbb eax, eax ; 如果申请空间过大,eax = 0xFFFFFFFF,否则eax = 0
    not eax ;
    and ecx, eax ; ecx = 0 | ecx = ecx
    mov eax, esp ;
    and eax, not ( _PAGESIZE_ - 1) ; 得到当前栈位置所处页面地址
    cs10:
    cmp ecx, eax ;
    jb short cs20 ; 如果新的栈位置小于页面地址
    mov eax, ecx ;
    pop ecx
    xchg esp, eax ; 更新esp,原始esp存储在eax中
    mov eax, dword ptr [eax] ; 当前esp指向返回地址
    mov dword ptr [esp], eax ; 修正函数栈帧,使其可以正确返回
    ret
    cs20:
    sub eax, _PAGESIZE_ ; 获取上一个页面
    test dword ptr [eax],eax ; 探测页面权限
    jmp short cs10 ; 如果没有产生异常则跳转,如果出现异常,则直接进入父函数的异常处理中

    _chkstk endp
    end
    calloc
    void* calloc(size_t num,size_t size);
      calloc用来分配数组空间,同样返回指针是根据对象类型对齐的,每个对象都被初始化为0,如果待分配内存超过_HEAP_MAXREQ或分配失败则设置errno为ENOMEM,calloc内部调用了malloc函数使用_set_new_mode函数设置回调模式,该回调用于处理分配失败情况,默认情况下,分配失败后malloc不会调用新回调分配内存,然后我们可以通过提前调用_set_new_mode(1)或者链接NEWMODE.OBJ修改这种默认行为.calloc用法如下:

    #include <stdio.h>
    #include <malloc.h>

    int main( void )
    {
    long *buffer;

    buffer = (long *)calloc( 40, sizeof( long ) );
    if( buffer != NULL )
    printf( "Allocated 40 long integers " );
    else
    printf( "Can't allocate memory " );
    free( buffer );
    }
    Calloc源码:

    // calloc.c和calloc_impl.c
    void * __cdecl _calloc_base (size_t num, size_t size)
    {
    int errno_tmp = 0;
    void * pv = _calloc_impl(num, size, &errno_tmp);

    if ( pv == NULL && errno_tmp != 0 && _errno())
    {
    errno = errno_tmp; // recall, #define errno *_errno()
    }
    return pv;
    }
    void * __cdecl _calloc_impl (size_t num, size_t size, int * errno_tmp)
    {
    size_t size_orig;
    void * pvReturn;

    /* ensure that (size * num) does not overflow */
    if (num > 0)
    {
    _VALIDATE_RETURN_NOEXC((_HEAP_MAXREQ / num) >= size, ENOMEM, NULL);
    }
    size_orig = size = size * num;


    /* force nonzero size */
    if (size == 0)
    size = 1;

    for (;;)
    {
    pvReturn = NULL;

    if (size <= _HEAP_MAXREQ)
    {
    if (pvReturn == NULL)
    pvReturn = HeapAlloc(_crtheap, HEAP_ZERO_MEMORY, size);
    }

    if (pvReturn || _newmode == 0)
    {
    RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, size_orig, 0));
    if (pvReturn == NULL)
    {
    if ( errno_tmp )
    *errno_tmp = ENOMEM;
    }
    return pvReturn;
    }

    /* call installed new handler */
    if (!_callnewh(size))
    {
    if ( errno_tmp )
    *errno_tmp = ENOMEM;
    return NULL;
    }

    /* new handler was successful -- try to allocate again */
    }
    }
      现在来分析_calloc_impl执行流程:

    1.先检查申请大小是否超出门限,若申请大小为0则强制为1
    2.使用HeapAlloc分配内存并清零。如果成功则返回,否则执行_callnewh,即定义的失败处理函数,如果该回调函数返回0则原函数返回0退出,如果该回调函数返回非0,则原函数重复执行2直到成功。(_callnewh最终调用了NtQueryInformationProcess 0x24)
      可见calloc并没有像MSDN说的那样调用了malloc。。。另外,没看到有异常处理机制。

    _expand
      用于扩展或缩小已分配内存,用于改变已分配内存区大小。void* _expand(void* memblock,size_t newsize);
      该函数会检测地址的内存权限,如果不通过移动内存无法得到足够的空间,该函数会返回空,该函数不会分配小于请求大小的内存区。该函数不通过移动内存块的方式增缩已分配堆内存,在64位下该函数不会缩小内存区,大小小于16k的内存块都是在低碎片堆中分配的,在这种情况下_expand不会对内存块做任何变动直接返回memblock。同样该函数会检测参数合法性,且size不能超过门限值_HEAP_MAXREQ。

    #include <stdio.h>
    #include <malloc.h>
    #include <stdlib.h>

    int main( void )
    {
    char *bufchar;
    printf( "Allocate a 512 element buffer " );
    if( (bufchar = (char *)calloc( 512, sizeof( char ) )) == NULL )
    exit( 1 );
    printf( "Allocated %d bytes at %Fp ",
    _msize( bufchar ), (void *)bufchar );
    if( (bufchar = (char *)_expand( bufchar, 1024 )) == NULL )
    printf( "Can't expand" );
    else
    printf( "Expanded block to %d bytes at %Fp ",
    _msize( bufchar ), (void *)bufchar );
    // Free memory
    free( bufchar );
    exit( 0 );
    }
    expand源码为:

    void * __cdecl _expand_base (void * pBlock, size_t newsize)
    {
    void * pvReturn;

    size_t oldsize;

    /* validation section */
    _VALIDATE_RETURN(pBlock != NULL, EINVAL, NULL);
    if (newsize > _HEAP_MAXREQ) {
    errno = ENOMEM;
    return NULL;
    }

    if (newsize == 0)
    {
    newsize = 1;
    }

    oldsize = (size_t)HeapSize(_crtheap, 0, pBlock);

    pvReturn = HeapReAlloc(_crtheap, HEAP_REALLOC_IN_PLACE_ONLY, pBlock, newsize);

    if (pvReturn == NULL)
    {
    /* 如果使用了低碎片堆则返回原指针. */
    if (oldsize <= 0x4000 /* 低碎片堆最多申请16KB内存 */
    && newsize <= oldsize && _is_LFH_enabled())
    pvReturn = pBlock;
    else
    errno = _get_errno_from_oserr(GetLastError());
    }

    if (pvReturn)
    {
    RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
    RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
    }

    return pvReturn;
    }
      可见_expand函数最终调用了HeapReAlloc函数。
      一个小插曲:其实这些内存管理的库函数普遍使用debug和release两个版本,debug源码在dbg*.c(pp)中可以找到,同时调试的时候是可以直接定位进去的,而release版函数通常在函数名所在文件,比如delete在delete.cpp中;而debug版源码内存管理函数通常会有一种_CrtMemBlockHeader的结构体,这些都需要自己摸索。

    realloc
    源码:

    void * __cdecl _realloc_base (void * pBlock, size_t newsize)
    {
    void * pvReturn;
    size_t origSize = newsize;

    // if ptr is NULL, call malloc
    if (pBlock == NULL)
    return(_malloc_base(newsize));

    // if ptr is nonNULL and size is zero, call free and return NULL
    if (newsize == 0)
    {
    _free_base(pBlock);
    return(NULL);
    }


    for (;;) {

    pvReturn = NULL;
    if (newsize <= _HEAP_MAXREQ)
    {
    if (newsize == 0)
    newsize = 1;
    pvReturn = HeapReAlloc(_crtheap, 0, pBlock, newsize);
    }
    else
    {
    _callnewh(newsize);
    errno = ENOMEM;
    return NULL;
    }

    if ( pvReturn || _newmode == 0)
    {
    if (pvReturn)
    {
    RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
    RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
    }
    else
    {
    errno = _get_errno_from_oserr(GetLastError());
    }
    return pvReturn;
    }

    // call installed new handler
    if (!_callnewh(newsize))
    {
    errno = _get_errno_from_oserr(GetLastError());
    return NULL;
    }

    // new handler was successful -- try to allocate again
    }
    }
    得到realloc->HeapReAlloc

    malloc
    源码:

    void * __cdecl _malloc_base (size_t size)
    {
    void *res = NULL;
    // validate size
    if (size <= _HEAP_MAXREQ) {
    for (;;) {
    // allocate memory block
    res = _heap_alloc(size);
    // if successful allocation, return pointer to memory
    // if new handling turned off altogether, return NULL
    if (res != NULL)
    {
    break;
    }
    if (_newmode == 0)
    {
    errno = ENOMEM;
    break;
    }
    // call installed new handler
    if (!_callnewh(size))
    break;
    // new handler was successful -- try to allocate again
    }
    } else {
    _callnewh(size);
    errno = ENOMEM;
    return NULL;
    }
    RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));
    if (res == NULL)
    {
    errno = ENOMEM;
    }
    return res;
    }
    __forceinline void * __cdecl _heap_alloc (size_t size)
    {
    if (_crtheap == 0) {
    _FF_MSGBANNER(); /* write run-time error banner */
    _NMSG_WRITE(_RT_CRT_NOTINIT); /* write message */
    __crtExitProcess(255); /* normally _exit(255) */
    }
    return HeapAlloc(_crtheap, 0, size ? size : 1);
    }
    得到malloc->_heap_alloc->HeapAlloc

    free
    源码:

    void __cdecl _free_base (void * pBlock)
    {
    int retval = 0;
    if (pBlock == NULL)
    return;
    RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
    retval = HeapFree(_crtheap, 0, pBlock);
    if (retval == 0)
    {
    errno = _get_errno_from_oserr(GetLastError());
    }
    }
    得到free->HeapFree

    现在所有问题都集中在了HeapAlloc HeapFree HeapReAlloc上
    ---------------------
    作者:weixin_33809981
    来源:CSDN
    原文:https://blog.csdn.net/weixin_33809981/article/details/86787854
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    CRM 跳转到数据修改页面、动态生成model,form、增加新增页面——第21天
    CRM 日期字段过滤功能——第21天
    CRM多条件查询——第20天
    CRM排序——第19天
    CRM_分页显示——第18天
    CRM分页 ——第17天
    CRM多条件筛选和分页——第16天
    python global、nonlocal、闭包、作用域——第10天
    uniAPP view 和 swiper
    uniAPP tabBar 设置
  • 原文地址:https://www.cnblogs.com/Fightingbirds/p/10831802.html
Copyright © 2011-2022 走看看