重識(shí)new


layout: post
title: 重識(shí)new
categories: C/C++
description: 重識(shí)new
keywords:
url: https://lichao890427.github.io/ https://github.com/lichao890427/


一喇颁、new primer

??new操作符并不是c++的專屬杈抢,c運(yùn)行庫(kù)有new.h頭文件。C++中的new操作符捅位,眾所周知是用來(lái)分配動(dòng)態(tài)內(nèi)存的,而要能達(dá)到“動(dòng)態(tài)”這種靈活性的特征羞芍,非堆區(qū)莫屬分井,因?yàn)槎褏^(qū)支持手動(dòng)分配。如果內(nèi)存空間不夠洪唐,new操作符返回空,或拋出異常(下面會(huì)講述3種new操作符吼蚁,常規(guī)new操作符凭需、定位new操作符和禁止拋出異常的new操作符,如果禁止拋異常肝匆,那么只好返回空了)下面代碼為MSDN中的粒蜈,檢測(cè)new分配是否成功,內(nèi)存分配失敗處理還可以通過(guò)_set_new_handler注冊(cè)分配異常函數(shù)結(jié)果

// 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;
   }
}

??對(duì)于對(duì)象使用new操作符的情況旗国,生成的代碼執(zhí)行的流程一般為:先調(diào)用內(nèi)部合適的new函數(shù)枯怖,該函數(shù)最終調(diào)用內(nèi)存分配API進(jìn)行堆內(nèi)存分配,同時(shí)初始化一些方便內(nèi)存管理的結(jié)構(gòu)體和數(shù)據(jù)能曾。成功分配后度硝,將該地址視為this,如有必要?jiǎng)t設(shè)置虛表指針寿冕,之后調(diào)用構(gòu)造函數(shù)對(duì)該地址處對(duì)象進(jìn)行構(gòu)造蕊程。構(gòu)造函數(shù)一般也是執(zhí)行初始化功能而已,修改修改數(shù)據(jù)啥的驼唱。這些都是老生常談不再贅述藻茂。
??即使請(qǐng)求的空間是0字節(jié)大小,new也會(huì)返回不同的地址玫恳,也就說(shuō)總是在不同區(qū)域分配辨赐。這里注意和空類的區(qū)別,空類的大小是1京办,傳給new函數(shù)(該函數(shù)在之后給出)之后掀序,new函數(shù)接受到的參數(shù)只會(huì)>=1,而對(duì)于特殊情況:char* pch=new char[0];new函數(shù)接受的參數(shù)確實(shí)是0臂港,不過(guò)分配的空間并不是0字節(jié),因?yàn)榇嬖趦?nèi)存管理相關(guān)的結(jié)構(gòu)體也會(huì)占用內(nèi)存。
??new操作符有2種作用域审孽,一種是全局new县袱,一種是類作用域new。用戶在自定義類中可以重載自定義new函數(shù)佑力。代碼如下:

#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;
}
// 對(duì)于Blanks對(duì)象式散,全局new操作符已被替換,因此下面的代碼將分配sizeof(Blanks)大小的空間并把數(shù)據(jù)賦值為0xa5
int main()
{
   Blanks *a5 = new(0xa5) Blanks;
   return a5 != 0;
}

二打颤、new primer plus

??new和delete操作符是通過(guò)一種稱為自由存儲(chǔ)區(qū)的內(nèi)存池分配內(nèi)存的暴拄,而new和delete操作符本身在編譯時(shí)會(huì)由編譯器選擇合適的new函數(shù)和delete函數(shù)進(jìn)行實(shí)現(xiàn)。C運(yùn)行庫(kù)的new函數(shù)會(huì)在失敗時(shí)拋出std::bad_alloc異常编饺,如果要使用不拋出異常的new版本乖篷,則需要鏈接nothrownew.obj,然而一旦鏈接了該文件透且,標(biāo)準(zhǔn)C++庫(kù)中的new就不起作用了撕蔼。new有2種語(yǔ)法形式

  • [::] new [placement] new-type-name [new-initializer]
  • [::] new [placement] ( type-name ) [new-initializer]

2種new函數(shù)原型為:

  • 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形式實(shí)現(xiàn)

??第一種形式為常規(guī)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函數(shù)實(shí)現(xiàn)可以看到是使用malloc函數(shù)進(jìn)行分配秽誊,如果失敗則調(diào)用_callnewh調(diào)用注冊(cè)過(guò)的“new操作符失敗回調(diào)”函數(shù)(注冊(cè)用_set_new_handler)鲸沮,如果原先沒(méi)注冊(cè)new失敗回調(diào),則拋出bad_alloc異常锅论,可見(jiàn)在默認(rèn)情況下讼溺,該while只會(huì)執(zhí)行1次,僅當(dāng)自定義new失敗回調(diào)函數(shù)返回true最易,才可能多次嘗試分配怒坯。
??該種形式delete實(shí)現(xiàn)方式,單步以后vs不能定位到源碼耘纱,不過(guò)我們可以換一種思路敬肚,既然知道一定執(zhí)行析構(gòu)函數(shù),那么就在析構(gòu)中下斷點(diǎn)束析,斷下后查看反匯編艳馒,并執(zhí)行到上一級(jí)調(diào)用即可找到delete實(shí)現(xiàn)方法,因此看匯編實(shí)現(xiàn)员寇,發(fā)現(xiàn)是一個(gè)名為“scalar deleting destructor”的內(nèi)部函數(shù):

// 
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常見(jiàn)頭弄慰,無(wú)需理會(huì)
00EB3490  mov         dword ptr [this],ecx 
00EB3493  mov         ecx,dword ptr [this]  
00EB3496  call        MyClass::~MyClass (0EB1023h)  //執(zhí)行析構(gòu)
00EB349B  mov         eax,dword ptr [ebp+8]  
00EB349E  and         eax,1  
00EB34A1  je          MyClass::`scalar deleting destructor'+3Fh (0EB34AFh)  //如果傳入?yún)?shù)允許釋放則進(jìn)行調(diào)用對(duì)應(yīng)delete函數(shù)(對(duì)于定位new對(duì)應(yīng)的delete該參數(shù)是設(shè)置為不允許的)
00EB34A3  mov         eax,dword ptr [this]  
00EB34A6  push        eax 
00EB34A7  call        operator delete (0EB1154h)  
00EB34AC  add         esp,4  
00EB34AF  mov         eax,dword ptr [this]  //以下是無(wú)關(guān)的收尾工作
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 

??執(zhí)行到call operator delete這行,步入之后轉(zhuǎn)到源碼蝶锋,可以看到使用的是dbgdel.cpp的delete函數(shù)陆爽。實(shí)現(xiàn)如下

// dbgdel.cpp
void operator delete( void *pUserData )
{
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK);  /* 阻塞其他線程*/
    __TRY
        /* 得到用于內(nèi)存塊信息頭指針*/
        pHead = pHdr(pUserData);
        /* 檢查區(qū)塊類型 */
        _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
        _free_dbg( pUserData, pHead->nBlockUse );//調(diào)用free函數(shù)釋放內(nèi)存
    __FINALLY
        _munlock(_HEAP_LOCK);  /* 解鎖其他線程*/
    __END_TRY_FINALLY
    return;
}

探究第二種new形式實(shí)現(xiàn)

??第二種方式為不拋出異常的new,MyClass* fPtr2 = new( nothrow ) MyClass;扳缕,可見(jiàn)慌闭,這里用try塊捕獲了異常别威,因此不再拋出異常,余下的就是調(diào)用常規(guī)new函數(shù)而已驴剔。

// 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形式實(shí)現(xiàn)

??第三種方式為布局new:
char x1[sizeof( MyClass )];MyClass* fPtr3 = new( &x1[0] ) MyClass;
??這里所謂的布局就是說(shuō)告訴new我們已經(jīng)有一個(gè)內(nèi)存位置了:

// new
inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
{   // construct array with placement at _Where
    return (_Where);
}

??可以發(fā)現(xiàn)該new什么都沒(méi)做省古,那么為什么還要new呢?仔細(xì)想想可以知道丧失,編譯器對(duì)new的處理是調(diào)用new函數(shù)后豺妓,之后將該地址作為this指針進(jìn)行初始化操作(比如設(shè)置虛表),再調(diào)用構(gòu)造函數(shù)布讹,而構(gòu)造函數(shù)這玩意不能直接調(diào)用琳拭,不像析構(gòu)函數(shù)那樣,因?yàn)闃?gòu)造之前還沒(méi)有對(duì)象和指針呢描验,對(duì)象和指針是構(gòu)造以后才有的白嘁,而調(diào)用析構(gòu)函數(shù)的時(shí)候,是已經(jīng)有對(duì)象或指針的挠乳。所以這種定位new在我理解权薯,就是可以相當(dāng)于可以直接構(gòu)造了。
??從上面可以看到new操作符先調(diào)用合適的new函數(shù)分配空間睡扬,之后調(diào)用構(gòu)造函數(shù)構(gòu)造盟蚣,而delete函數(shù)剛好相對(duì),先進(jìn)行析構(gòu)之后調(diào)用析構(gòu)函數(shù)析構(gòu)卖怜;同時(shí)可以看到布局new操作符的好處是可以手動(dòng)指定構(gòu)造和析構(gòu)的時(shí)間屎开,對(duì)于new無(wú)論哪種形式,在調(diào)用new函數(shù)分配好內(nèi)存后都會(huì)調(diào)用構(gòu)造函數(shù)進(jìn)行構(gòu)造马靠,而定位new函數(shù)實(shí)則是直接返回奄抽,這就導(dǎo)致直接使用當(dāng)前地址進(jìn)行構(gòu)造,相當(dāng)于顯示調(diào)用構(gòu)造函數(shù),而析構(gòu)時(shí)由于沒(méi)有實(shí)際分配空間甩鳄,因此不能用delete逞度,而是顯示調(diào)用析構(gòu)函數(shù)進(jìn)行析構(gòu)。
??上面都是對(duì)于有構(gòu)造函數(shù)和析構(gòu)函數(shù)對(duì)象的情況妙啃,用delete時(shí)档泽,編譯器會(huì)為該類專門生成一個(gè)scalar deleting destructor函數(shù),該函數(shù)中先進(jìn)行析構(gòu)揖赴,之后調(diào)用operator delete函數(shù)代赁。當(dāng)然职恳,如果沒(méi)有析構(gòu)函數(shù),那么就不會(huì)有scalar deleting destructor函數(shù)了危虱,此時(shí)單步是可以看到delete源碼的鸳吸,即dbgdel.cpp中的void operator delete(void *pUserData)函數(shù)林束。這一點(diǎn)在delete用于基本類型時(shí)顯而易見(jiàn)。

探究第一種new[]形式實(shí)現(xiàn)

??第一種類型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[]函數(shù)的參數(shù)count是sizeof(MyClass[2])+sizeof(int)恃锉,該sizeof(int)用于內(nèi)存管理。new仍然調(diào)用了new();沒(méi)有本質(zhì)區(qū)別呕臂,即這么多對(duì)象占用的內(nèi)存是當(dāng)作整體分配的淡喜。再分配好之后,就需要對(duì)每個(gè)對(duì)象this指針處進(jìn)行初始化和構(gòu)造了诵闭。
??通過(guò)逆向分析可知先調(diào)用了new[],如果成功分配內(nèi)存澎嚣,則調(diào)用數(shù)組構(gòu)造迭代器vector_constructor_iterator對(duì)每個(gè)對(duì)象進(jìn)行構(gòu)造疏尿。void* base=new[](sizeof(int)+sizeof(MyClass[2]));,起始4字節(jié)存儲(chǔ)要初始化的對(duì)象個(gè)數(shù)易桃,剩余空間為對(duì)象占用內(nèi)存

                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個(gè)對(duì)象
  vector_construtor_iterator((MyClass*)((char*)base+4),sizeof(MyClass[2]),2,&MyClass::MyClass,&MyClass::~MyClass);
}

vector_constructor_iterator對(duì)應(yīng)代碼為:

 ; 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++語(yǔ)法:

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();//用構(gòu)造函數(shù)構(gòu)造
        }       
    }
    __except(1)
    {
        __ArrayUnwind(objs,size,i,pDtor);//如果某個(gè)構(gòu)造函數(shù)產(chǎn)生異常褥琐,則進(jìn)行棧解退,用到析構(gòu)函數(shù) 
    }
}

??棧解退晤郑,這里引用C++ Primer Plus的解釋:“現(xiàn)在假設(shè)函數(shù)由于出現(xiàn)異常而終止(而不是由于返回)敌呈,則程序也將釋放棧中的內(nèi)存,但不會(huì)在釋放棧的第一個(gè)返回地址后停止造寝,而是繼續(xù)釋放棧磕洪,直到找到一個(gè)位于try塊中的返回地址,隨后控制權(quán)將轉(zhuǎn)到塊尾的異常處理程序诫龙,而不會(huì)函數(shù)調(diào)用后面的第一條語(yǔ)句析显。這個(gè)過(guò)程稱為棧解退,引發(fā)機(jī)制的一個(gè)非常重要的特性是签赃,和函數(shù)返回一樣谷异,對(duì)于棧中的自動(dòng)類對(duì)象,而throw語(yǔ)句則處理try塊和throw之間的整個(gè)函數(shù)調(diào)用徐麗放在棧中的對(duì)象锦聊。如果沒(méi)有棧解退這種特性歹嘹,則引發(fā)異常后,對(duì)于中間函數(shù)調(diào)用放在棧中的自動(dòng)類對(duì)象孔庭,其析構(gòu)函數(shù)將不會(huì)被調(diào)用尺上。”史飞。unwind就是解退的意思尖昏,現(xiàn)在來(lái)查看__ArrayUnwind的源碼,根據(jù)函數(shù)名可知該函數(shù)用于對(duì)象數(shù)組解退:


                 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]對(duì)象由于沒(méi)有構(gòu)造成功构资,因此從[count-1]個(gè)對(duì)象開(kāi)始析構(gòu)
    __try
    {
        while(count--)
        {
           objs--;
           objs->pDtor();
        }
    }
    __except(terminate(),0)//如果析構(gòu)發(fā)生異常抽诉,則終止程序
    {
        return;
    }
}

探究第一種形式delete[]形式實(shí)現(xiàn)

                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成功,則往下執(zhí)行
                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

??可見(jiàn)vector_deleting_destructor是用來(lái)析構(gòu)對(duì)象數(shù)組的吐绵,原型為void* __thiscall MyClass::vector_deleting_destructor(usigned int flag);迹淌,該函數(shù)是編譯器內(nèi)部為MyClass類加的成員函數(shù)
flag含義未知河绽,所以需要分析該函數(shù)源碼:

                 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

經(jīng)過(guò)逆向分析得到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);
      }
    }
}

僅從以上代碼可以分析出以下幾點(diǎn):

  • 1.this-4這個(gè)地址為之前new成功分配所返回值耙饰,可以將其看成sizeof(int)+sizeof(MyClass[2])大小的結(jié)構(gòu)體,第一個(gè)成員為對(duì)象個(gè)數(shù)纹份。
  • 2.該函數(shù)對(duì)數(shù)組和非數(shù)組進(jìn)行了分別處理苟跪,可以分析出第2個(gè)二進(jìn)制位為1時(shí),是析構(gòu)對(duì)象數(shù)組蔓涧,為0時(shí)是析構(gòu)普通對(duì)象件已。而第1個(gè)二進(jìn)制位是規(guī)定是否釋放內(nèi)存,可以想象如果這里是定位new元暴,那么這里是不應(yīng)該釋放的篷扩。
  • 3.vector_destructor_iterator起實(shí)際析構(gòu)作用原型void __stdcall vector_destructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pDtor)(void *));,下面來(lái)看該函數(shù)
                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

經(jīng)過(guò)逆向分析得到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;//從最后一個(gè)對(duì)象開(kāi)始析構(gòu)
        while(count--)
        {
           last->pDtor();
           last--;
        }
    }
    __except(1)
    {
        __ArrayUnwind(objs,size,count,pDtor);//如果某個(gè)析構(gòu)函數(shù)產(chǎn)生異常茉盏,則跳過(guò)該對(duì)象鉴未,繼續(xù)析構(gòu)之前的對(duì)象 
    }
}

??鑒于__ArrayUnwind前面已經(jīng)介紹過(guò),這里就不分析了鸠姨。如果仔細(xì)分析上一節(jié)開(kāi)頭給出main匯編代碼铜秆,會(huì)發(fā)現(xiàn)只要new成功了,delete都會(huì)去執(zhí)行析構(gòu)讶迁,即使出現(xiàn)對(duì)象數(shù)組中某個(gè)對(duì)象構(gòu)造失敗導(dǎo)致已經(jīng)進(jìn)行析構(gòu)羽峰,delete時(shí)所有元素仍會(huì)析構(gòu)一次。

探究第二種情況new[]形式實(shí)現(xiàn)

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));
    }

可見(jiàn)調(diào)用了單對(duì)象的第二種new形式添瓷,與非數(shù)組形式類似梅屉,不再贅述

探究第三種情況new[]形式實(shí)現(xiàn)

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);
}

??可見(jiàn)等同于對(duì)象第三種new形式,不再贅述鳞贷。下面我們來(lái)看看其他內(nèi)存分配函數(shù)

malloca

void* _malloca(size_t size);
??MSDN里是這么描述的:在棧上分配內(nèi)存坯汤,是_alloca的安全性增強(qiáng)版本。返回指針是根據(jù)對(duì)象大小對(duì)齊搀愧,如果size是0則返回長(zhǎng)度0的合法指針惰聂。如果地址空間無(wú)法分配會(huì)拋出一個(gè)棧溢出異常,該異常不是C++異常咱筛,需要使用SEH搓幌。_malloca和_alloca的區(qū)別在于_alloc無(wú)論大小總是在棧上分配,且無(wú)需free釋放內(nèi)存迅箩。而_malloca需要使用_freea釋放內(nèi)存溉愁,在調(diào)試模式下,_malloca總是在堆上分配。在異常處理時(shí)顯式調(diào)用_malloca有一些限制饲趋,x86架構(gòu)處理器異常處理例程會(huì)自動(dòng)控制函數(shù)棧幀拐揭,在執(zhí)行操作時(shí)并不基于當(dāng)前閉合函數(shù)棧幀撤蟆,這一點(diǎn)在Windows NT SEH和C++異常處理的catch語(yǔ)句中很常見(jiàn)。因此在以下情況顯示調(diào)用_malloca堂污,在執(zhí)行異常處理例程后會(huì)產(chǎn)生程序崩潰家肯。
??Windows NT SEH異常過(guò)濾表達(dá)式:__except(_malloca())
??Windows NT SEH最終執(zhí)行表達(dá)式:__finally(_malloca())
??C++ 異常處理 catch語(yǔ)句
??然而_malloca可以從異常處理例程中除上述情況以外的情況下直接調(diào)用,或在異常處理所觸發(fā)的回調(diào)函數(shù)中調(diào)用也是允許的盟猖。先來(lái)看一個(gè)例子:

#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!\n");
 
        // 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版下讨衣,宏調(diào)用了malloc進(jìn)行分配,之后使用_MarkAllocaS對(duì)分配內(nèi)存進(jìn)行一些處理(后面討論)式镐,而RELEASE版下值依,宏先判斷要分配的內(nèi)存是否過(guò)大,該門限為_(kāi)ALLOCA_S_THRESHOLD-_ALLOCA_S_MARKER_SIZE=1016碟案,如果超過(guò)該值則調(diào)用malloc,否則調(diào)用_alloca颇蜡。從字面意思上可以知道_ALLOCA_S_HEAP_MARKER這個(gè)標(biāo)志位說(shuō)明該內(nèi)存區(qū)是在堆上分配的价说,而_ALLOCA_S_STACK_MARKER標(biāo)志是在棧上分配的。在malloc或_alloca分配成功后风秤,總會(huì)調(diào)用_MarkAllocaS進(jìn)行調(diào)整鳖目。結(jié)合字面意思和5行C語(yǔ)言代碼可知,在執(zhí)行過(guò)內(nèi)存分配后缤弦,返回的指針前sizeof(unigned int*)字節(jié)為分配內(nèi)存類型標(biāo)志领迈,之后指針調(diào)整到空閑位置丟給用戶操作。那么所有的問(wèn)題都落在_alloca和malloc的源碼上碍沐,下面會(huì)進(jìn)行分析狸捅。
??_alloca(我第一次見(jiàn)棧上分配內(nèi)存是在逆向一個(gè)易語(yǔ)言程序時(shí),用的是sub esp累提,而微軟這個(gè)函數(shù)是第一次見(jiàn))void* _alloca(size_t size);尘喝,該函數(shù)只在程序棧中分配字節(jié),而函數(shù)退出時(shí)該空間會(huì)自動(dòng)釋放斋陪,因此無(wú)需手動(dòng)釋放朽褪。用此函數(shù)的限制和_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缔赠,因?yàn)開(kāi)alloca拋出SEH而不是C++異常
    __try {
        // 使用_alloca分配太大的空間很容易崩潰,推薦1024字節(jié)以下的空間  
        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.\n");
        }
    }
 
    // 如果溢出
    __except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
    {
        printf_s("_alloca failed!\n");
 
        // 使用下面的函數(shù)恢復(fù)函數(shù)棧
        errcode = _resetstkoflw();
        if (errcode)
        {
            printf_s("Could not reset the stack!\n");
            _exit(1);
        }
    };
}

來(lái)看反匯編

; 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.\n"
                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!\n"
                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!\n"
                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調(diào)用之前嗤堰,發(fā)生了add eax,24h和mov eax,[ebp+cbSize],而在之后發(fā)生了mov [ebp+pAllocaBase], esp度宦,那么大膽做出猜測(cè):

  • 1.add eax,24h梁棠,說(shuō)明這24h字節(jié)用來(lái)實(shí)現(xiàn)內(nèi)存管理或字節(jié)對(duì)齊之類功能
  • 2.__alloca_probe_16為接受一個(gè)參數(shù)的函數(shù)置森,該參數(shù)通過(guò)eax傳遞,進(jìn)行的操作是修改esp符糊,因此esp可以看做執(zhí)行結(jié)果
  • 3.另一個(gè)函數(shù)原型為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);
    ......
}
 
//這個(gè)結(jié)構(gòu)體來(lái)自于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)
{//初始化已分配空間,可以用于維護(hù)調(diào)試版函數(shù)棧
  if(pAllocaBase && cbSize && pAllocaInfoList)//由于最后一個(gè)參數(shù)在本例中為0男娄,這個(gè)函數(shù)實(shí)際相當(dāng)于沒(méi)有執(zhí)行
  {
     memset(pAllocaBase,0xCC,cbSize);//經(jīng)常在調(diào)試版程序椥刑埃空間看到0xCC  "燙燙燙燙燙"  對(duì)吧,就是這樣的模闲。建瘫。。
     pAllocaBase->next=*pAllocaInfoList;//鏈接到前一個(gè)結(jié)構(gòu)尸折;
     pAllocaBase->allocaSize=cbSize;
     *pAllocaInfoList=pAllocaBase;//自此可知啰脚,上述結(jié)構(gòu)形成鏈表,pAllocaInfoList指向當(dāng)前結(jié)構(gòu)
  }
}
//__alloca_probe_16代碼下面會(huì)進(jìn)行分析

??以上是debug版的情況实夹,如果嘗試用release版查看反匯編代碼橄浓,會(huì)發(fā)現(xiàn)只有push和call __alloca_probe_16部分,可知add eax,24和AllocaHelper只是調(diào)試版本用于內(nèi)存管理的亮航。所以重點(diǎn)落在該函數(shù)的解析上荸实。進(jìn)入源代碼查看,__alloca_probe_16用來(lái)按16字節(jié)對(duì)齊內(nèi)存缴淋,而chkstk子例程進(jìn)行實(shí)際分配操作:

// alloca16.asm

; _alloca_probe_16, _alloca_probe_8 - 按8/16字節(jié)對(duì)齊例程
;輸入:EAX = 棧幀大小
;輸出:調(diào)整EAX准给,修改esp.
 
public  _alloca_probe_8
_alloca_probe_16 proc                   ; 16 byte aligned alloca
 
        push    ecx
        lea     ecx, [esp] + 8          ; 父函數(shù)棧頂(call _alloca_probe_16和push ecx)
        sub     ecx, eax                ; 
        and     ecx, (16 - 1)           ; 計(jì)算地址低4位未對(duì)齊偏移
        add     eax, ecx                ; 增加cbSize使其對(duì)齊
        sbb     ecx, ecx                ; 如果cbSize溢出,ecx = 0xFFFFFFFF重抖,否則ecx = 0
        or      eax, ecx                ; 如果溢出露氮,則eax = 0xFFFFFFFF
        pop     ecx                     ; 還原ecx
        jmp     _chkstk         ; eax存儲(chǔ)修正cbSize,并交給_chkstk處理
_alloca_probe_16 endp
 
        end
 
 
public  _alloca_probe
_chkstk proc
_alloca_probe    =  _chkstk
        push    ecx
        lea     ecx, [esp] + 8 - 4      ; 考慮到之后的ret指令對(duì)未來(lái)esp的修改
        sub     ecx, eax                ; 分配椫优妫空間沦辙,ecx存儲(chǔ)更新后的棧位置
        sbb     eax, eax                ; 如果申請(qǐng)空間過(guò)大,eax = 0xFFFFFFFF讹剔,否則eax = 0
        not     eax                     ; 
        and     ecx, eax                ; ecx = 0 | ecx = ecx
        mov     eax, esp                ;
        and     eax, not ( _PAGESIZE_ - 1) ; 得到當(dāng)前棧位置所處頁(yè)面地址
cs10:
        cmp     ecx, eax                ; 
        jb      short cs20              ; 如果新的棧位置小于頁(yè)面地址
        mov     eax, ecx                ; 
        pop     ecx
        xchg    esp, eax                ; 更新esp油讯,原始esp存儲(chǔ)在eax中
        mov     eax, dword ptr [eax]    ; 當(dāng)前esp指向返回地址
        mov     dword ptr [esp], eax    ; 修正函數(shù)棧幀,使其可以正確返回
        ret
cs20:
        sub     eax, _PAGESIZE_         ; 獲取上一個(gè)頁(yè)面
        test    dword ptr [eax],eax     ; 探測(cè)頁(yè)面權(quán)限
        jmp     short cs10      ; 如果沒(méi)有產(chǎn)生異常則跳轉(zhuǎn)延欠,如果出現(xiàn)異常陌兑,則直接進(jìn)入父函數(shù)的異常處理中
 
_chkstk endp
        end

calloc

void* calloc(size_t num,size_t size);
??calloc用來(lái)分配數(shù)組空間,同樣返回指針是根據(jù)對(duì)象類型對(duì)齊的由捎,每個(gè)對(duì)象都被初始化為0兔综,如果待分配內(nèi)存超過(guò)_HEAP_MAXREQ或分配失敗則設(shè)置errno為ENOMEM,calloc內(nèi)部調(diào)用了malloc函數(shù)使用_set_new_mode函數(shù)設(shè)置回調(diào)模式,該回調(diào)用于處理分配失敗情況软驰,默認(rèn)情況下涧窒,分配失敗后malloc不會(huì)調(diào)用新回調(diào)分配內(nèi)存,然后我們可以通過(guò)提前調(diào)用_set_new_mode(1)或者鏈接NEWMODE.OBJ修改這種默認(rèn)行為.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\n" );
   else
      printf( "Can't allocate memory\n" );
   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 */
        }
}

??現(xiàn)在來(lái)分析_calloc_impl執(zhí)行流程:

  • 1.先檢查申請(qǐng)大小是否超出門限锭亏,若申請(qǐng)大小為0則強(qiáng)制為1
  • 2.使用HeapAlloc分配內(nèi)存并清零纠吴。如果成功則返回,否則執(zhí)行_callnewh慧瘤,即定義的失敗處理函數(shù)戴已,如果該回調(diào)函數(shù)返回0則原函數(shù)返回0退出,如果該回調(diào)函數(shù)返回非0锅减,則原函數(shù)重復(fù)執(zhí)行2直到成功糖儡。(_callnewh最終調(diào)用了NtQueryInformationProcess 0x24)

??可見(jiàn)calloc并沒(méi)有像MSDN說(shuō)的那樣調(diào)用了malloc。怔匣。握联。另外,沒(méi)看到有異常處理機(jī)制每瞒。

_expand

??用于擴(kuò)展或縮小已分配內(nèi)存金闽,用于改變已分配內(nèi)存區(qū)大小。void* _expand(void* memblock,size_t newsize);
??該函數(shù)會(huì)檢測(cè)地址的內(nèi)存權(quán)限独泞,如果不通過(guò)移動(dòng)內(nèi)存無(wú)法得到足夠的空間,該函數(shù)會(huì)返回空苔埋,該函數(shù)不會(huì)分配小于請(qǐng)求大小的內(nèi)存區(qū)懦砂。該函數(shù)不通過(guò)移動(dòng)內(nèi)存塊的方式增縮已分配堆內(nèi)存,在64位下該函數(shù)不會(huì)縮小內(nèi)存區(qū)组橄,大小小于16k的內(nèi)存塊都是在低碎片堆中分配的荞膘,在這種情況下_expand不會(huì)對(duì)內(nèi)存塊做任何變動(dòng)直接返回memblock。同樣該函數(shù)會(huì)檢測(cè)參數(shù)合法性玉工,且size不能超過(guò)門限值_HEAP_MAXREQ羽资。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
 
int main( void )
{
   char *bufchar;
   printf( "Allocate a 512 element buffer\n" );
   if( (bufchar = (char *)calloc( 512, sizeof( char ) )) == NULL )
      exit( 1 );
   printf( "Allocated %d bytes at %Fp\n", 
         _msize( bufchar ), (void *)bufchar );
   if( (bufchar = (char *)_expand( bufchar, 1024 )) == NULL )
      printf( "Can't expand" );
   else
      printf( "Expanded block to %d bytes at %Fp\n", 
            _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 /* 低碎片堆最多申請(qǐng)16KB內(nèi)存 */
                    && 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;
}

??可見(jiàn)_expand函數(shù)最終調(diào)用了HeapReAlloc函數(shù)。
??一個(gè)小插曲:其實(shí)這些內(nèi)存管理的庫(kù)函數(shù)普遍使用debug和release兩個(gè)版本遵班,debug源碼在dbg*.c(pp)中可以找到屠升,同時(shí)調(diào)試的時(shí)候是可以直接定位進(jìn)去的,而release版函數(shù)通常在函數(shù)名所在文件狭郑,比如delete在delete.cpp中腹暖;而debug版源碼內(nèi)存管理函數(shù)通常會(huì)有一種_CrtMemBlockHeader的結(jié)構(gòu)體,這些都需要自己摸索翰萨。

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

現(xiàn)在所有問(wèn)題都集中在了HeapAlloc HeapFree HeapReAlloc上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脏答,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殖告,老刑警劉巖阿蝶,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異黄绩,居然都是意外死亡羡洁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門宝与,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)焚廊,“玉大人,你說(shuō)我怎么就攤上這事习劫∨匚粒” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵诽里,是天一觀的道長(zhǎng)袒餐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)谤狡,這世上最難降的妖魔是什么灸眼? 我笑而不...
    開(kāi)封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮墓懂,結(jié)果婚禮上焰宣,老公的妹妹穿的比我還像新娘。我一直安慰自己捕仔,他們只是感情好匕积,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著榜跌,像睡著了一般闪唆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钓葫,一...
    開(kāi)封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天悄蕾,我揣著相機(jī)與錄音,去河邊找鬼础浮。 笑死帆调,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豆同。 我是一名探鬼主播贷帮,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诱告!你這毒婦竟也來(lái)了撵枢?” 一聲冷哼從身側(cè)響起民晒,我...
    開(kāi)封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锄禽,沒(méi)想到半個(gè)月后潜必,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沃但,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年磁滚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宵晚。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垂攘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出淤刃,到底是詐尸還是另有隱情晒他,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布逸贾,位于F島的核電站陨仅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏铝侵。R本人自食惡果不足惜灼伤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咪鲜。 院中可真熱鬧狐赡,春花似錦、人聲如沸疟丙。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)隆敢。三九已至发皿,卻和暖如春崔慧,著一層夾襖步出監(jiān)牢的瞬間拂蝎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工惶室, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留温自,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓皇钞,卻偏偏與公主長(zhǎng)得像悼泌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夹界,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350