1. 程式人生 > >C++中new的原始碼與行為分析

C++中new的原始碼與行為分析

這是我在處女面中遇到的一個問題,當時回答的不太好,因此下來研究、學習了一下。C++中operator new的行為及其與malloc的區別。當時我回答二者的區別是new在實現中會呼叫malloc並且由編譯器安插呼叫建構函式的程式碼,並在面試官前輩的提醒下又答出malloc失敗返回0,而new直接拋異常。但不清楚有沒有不拋異常的new和不呼叫建構函式的new這一點。下面結合程式碼分析一下。

19:     CTest *pTest = new CTest(3, 2);
0027652D 6A 08                push        8  
0027652F E8 8D AD FF FF       call
operator new (02712C1h) ; 預設的operator new 00276534 83 C4 04 add esp,4 00276537 89 85 08 FF FF FF mov dword ptr [ebp-0F8h],eax 0027653D C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 00276544 83 BD 08 FF FF FF 00 cmp dword ptr [ebp-0F8h],0 0027654B 74 17 je main+74
h (0276564h) 0027654D 6A 02 push 2 0027654F 6A 03 push 3 00276551 8B 8D 08 FF FF FF mov ecx,dword ptr [ebp-0F8h] 00276557 E8 B4 AE FF FF call CTest::CTest (0271410h) ; 呼叫建構函式 0027655C 89 85 E8 FE FF FF mov dword ptr [ebp-118h],eax 00276562
EB 0A jmp main+7Eh (027656Eh) 00276564 C7 85 E8 FE FF FF 00 00 00 00 mov dword ptr [ebp-118h],0 0027656E 8B 85 E8 FE FF FF mov eax,dword ptr [ebp-118h] 00276574 89 85 14 FF FF FF mov dword ptr [ebp-0ECh],eax 0027657A C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh 00276581 8B 8D 14 FF FF FF mov ecx,dword ptr [ebp-0ECh] 00276587 89 4D EC mov dword ptr [pTest],ecx 20: 21: CTest *pTest2 = new (std::nothrow) CTest(4, 6); 0027658A 68 D0 C2 27 00 push offset std::nothrow (027C2D0h) 0027658F 6A 08 push 8 00276591 E8 63 AC FF FF call operator new (02711F9h) ;多個引數,不拋異常的operator new 00276596 83 C4 08 add esp,8 00276599 89 85 F0 FE FF FF mov dword ptr [ebp-110h],eax 0027659F C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 002765A6 83 BD F0 FE FF FF 00 cmp dword ptr [ebp-110h],0 002765AD 74 17 je main+0D6h (02765C6h) 002765AF 6A 06 push 6 002765B1 6A 04 push 4 002765B3 8B 8D F0 FE FF FF mov ecx,dword ptr [ebp-110h] 002765B9 E8 52 AE FF FF call CTest::CTest (0271410h) ; 呼叫建構函式 002765BE 89 85 E8 FE FF FF mov dword ptr [ebp-118h],eax 20: 21: CTest *pTest2 = new (std::nothrow) CTest(4, 6); 002765C4 EB 0A jmp main+0E0h (02765D0h) 002765C6 C7 85 E8 FE FF FF 00 00 00 00 mov dword ptr [ebp-118h],0 002765D0 8B 85 E8 FE FF FF mov eax,dword ptr [ebp-118h] 002765D6 89 85 FC FE FF FF mov dword ptr [ebp-104h],eax 002765DC C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh 002765E3 8B 8D FC FE FF FF mov ecx,dword ptr [ebp-104h] 002765E9 89 4D E0 mov dword ptr [pTest2],ecx 22: 23: pTest->m_dw1 = 3; 002765EC 8B 45 EC mov eax,dword ptr [pTest] 002765EF C7 00 03 00 00 00 mov dword ptr [eax],3 24: pTest2->m_dw2 = 5; 002765F5 8B 45 E0 mov eax,dword ptr [pTest2] 002765F8 C7 40 04 05 00 00 00 mov dword ptr [eax+4],5 25: 26: 27: return 0; 002765FF 33 C0 xor eax,eax 28: }

operator new的原始碼:

void* __CRTDECL operator new(size_t const size)
{
    for (;;)
    {
        if (void* const block = malloc(size))
        {
            return block;
        }

        if (_callnewh(size) == 0)
        {
            if (size == SIZE_MAX)
            {
                __scrt_throw_std_bad_array_new_length();
            }
            else
            {
                __scrt_throw_std_bad_alloc();
            }
        }

        // The new handler was successful; try to allocate again...
    }
}

operator new的原始碼,嗯嗯,果然不可能拋異常,因為異常都被接住了:

void* __CRTDECL operator new(size_t const size, std::nothrow_t const&) noexcept
{
    try
    {
        return operator new(size);
    }
    catch (...)
    {
        return nullptr;
    }
}

delete操作都是一樣的,呼叫了帶兩個引數的operator delete,這個應該也是後來搞出來的一個函式,我印象還比較深。之前在嘗試將VS2017編譯的obj與WDK中lib版CRT庫匹配時曾遇到過這個帶了兩個引數的delete找不到匹配的符號,結果用了/Zc:sizedDealloc-開關才避免其生成。詳細過程請見這篇文章

   26:  delete(pTest);
013865FF 8B 45 EC             mov         eax,dword ptr [pTest]  
01386602 89 85 E4 FE FF FF    mov         dword ptr [ebp-11Ch],eax  
01386608 6A 08                push        8  
0138660A 8B 8D E4 FE FF FF    mov         ecx,dword ptr [ebp-11Ch]  
01386610 51                   push        ecx  
01386611 E8 3F AA FF FF       call        operator delete (01381055h)  
01386616 83 C4 08             add         esp,8  
    27:  delete(pTest2);
01386619 8B 45 E0             mov         eax,dword ptr [pTest2]  
0138661C 89 85 D8 FE FF FF    mov         dword ptr [ebp-128h],eax  
01386622 6A 08                push        8  
01386624 8B 8D D8 FE FF FF    mov         ecx,dword ptr [ebp-128h]  
0138662A 51                   push        ecx  
0138662B E8 25 AA FF FF       call        operator delete (01381055h)  
01386630 83 C4 08             add         esp,8  

在其中呼叫了帶一個引數的operator delete:

    28: void __CRTDECL operator delete(void* const block, size_t const) noexcept
    29: {
01384FF0 55                   push        ebp  
01384FF1 8B EC                mov         ebp,esp  
    30:     operator delete(block);
01384FF3 8B 45 08             mov         eax,dword ptr [block]  
    30:     operator delete(block);
01384FF6 50                   push        eax  
01384FF7 E8 36 C0 FF FF       call        operator delete (01381032h)  
01384FFC 83 C4 04             add         esp,4  
    31: }
01384FFF 5D                   pop         ebp  
01385000 C3                   ret  

而這個operator delete則呼叫了free:

void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
    #else
    free(block);
    #endif
}

結論:可以通過std::nothrow讓new不拋異常,VS17下的做法是在該new函式中用try捕獲了異常,並不再丟擲,同時讓其返回0;對於有建構函式的類物件,new時都會呼叫其建構函式,是由編譯器自動插入程式碼的。