C++--自定義內存管理

1、關於動態內存分配

2、new / delete的重載

3、重載new[] / delete[]

4、動態內存申請的結果

1、動態內存申請的結果

2、跨編譯器統一new的行爲

5、小結

1、關於動態內存分配

new關鍵字與malloc函數的區別

- new關鍵字是C++的—部分 ,malloc是由C庫提供的函數

- new以具體類型爲單位進行內存分配 ,malloc以字節爲單位進行內存分配

- new在申請內存空間時可進行初始化 ,malloc僅根據需要申請定量的內存空間

- new在所有C++編譯器中都被支持 ,malloc在某些系統開發中是不能調用

- new能夠觸發構造函數的調用 ,malloc僅分配需要的內存空間

- 對象的創建只能使用new ,malloc不適合面向對象開發

delete和free的區別

- delete在所有C++編譯器中都被支持 ,free在某些系統開發中是不能調用

- delete能夠觸發析構函數的調用 ,free僅歸還之前分配的內存空間

- 對象的銷燬只能使用delete ,free不適合面向對象開發

面試題:new關鍵字創建出來的對象位於什麼地方?

2、new / delete的重載

new / delete的本質是C++預定義的操作符

C++對這兩個操作符做了嚴格的行爲定義

☛ new:

① 獲取足夠大的內存空間(默認爲堆空間)

② 在獲取的空間中調用構造函數創建對象

☛ delete:

① 調用析構函數銷燬對象

② 歸還對象所佔用的空間(默認爲堆空間)

在C++中能夠重載new / delete操作符

- 全局重載(不推薦)

- 局部重載(針對具體類進行重載)

重載new / delete的意義在於改變動態對象創建時的內存分配方式

new / delete的重載方式

// static member function, 默認爲靜態成員函數
void* operator new (unsigned int size)
{
void* ret = NULL;

/* ret point to allocated memory */

return ret;

}
// static member function
void operator delete (void* p)
{
/* free the memory which is pointed by p */
}
編程實驗

靜態存儲區中創建動態對象  test.cpp

#include     
  
using namespace std;  
  
class Test  
{  
    static const unsigned int COUNT = 4;  
    static char c_buffer[];  
    static char c_map[]; 
      
    int m_value;  
public:  
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        for(int i=0; i<COUNT; i++)  // 在c_buffer中找到空閒位置
        {  
            if( !c_map[i] )  
            {  
                c_map[i] = 1;  
                  
                ret = c_buffer + i * sizeof(Test);  // 可用空間的首地址
                  
                cout << "succeed to allocate memory: " << ret << endl;  
                  
                break;  
            }  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            char* mem = reinterpret_cast<char*>§;

int index = (mem - c_buffer) / sizeof(Test);  
            int flag = (mem - c_buffer) % sizeof(Test);  
             
            if( (flag == 0) && (0 <= index) && (index < COUNT) )  
            {  
                c_map[index] = 0;  
                  
                cout << “succeed to free memory: " << p << endl;  
            }  
        }  
    }  
};  
  
char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};  
char Test::c_map[Test::COUNT] = {0};  
  
int main(int argc, char argv[])  
{  
    cout << "===== Test Single Object ===" << endl;  
       
    Test
 pt = new Test;  
      
    delete pt;  
      
    cout << "=== Test Object Array =====” << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << “pa[” << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}

面試題 :如何在指定的地址上創建C++對象?

解決方案

-在類中重載new / delete操作符

-在new的操作符重載函數中返回指定的地址

-在delete操作符重載中標記對應的地址可用

編程實驗

自定義動態對象的存儲空間   test.cpp

#include   
#include   
  
using namespace std;  
  
class Test  
{  
    static unsigned int c_count;  
    static char* c_buffer;  
    static char* c_map;  
      
    int m_value;  
public:  
    static bool SetMemorySource(char* memory, unsigned int size)  
    {  
        bool ret = false;  
          
        c_count = size / sizeof(Test);  
          
        ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));  
          
        if( ret )  
        {  
            c_buffer = memory;  
        }  
        else  
        {  
            free(c_map);  
              
            c_map = NULL;  
            c_buffer = NULL;  
            c_count = 0;  
        }  
          
        return ret;  
    }  
      
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        if( c_count > 0 )  
        {  
            for(int i=0; i<c_count; i++)  
            {  
                if( !c_map[i] )  
                {  
                    c_map[i] = 1;  
                      
                    ret = c_buffer + i * sizeof(Test);  
                      
                    cout << "succeed to allocate memory: " << ret << endl;  
                      
                    break;  
                }  
            }  
        }  
        else  
        {  
            ret = malloc(size);  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            if( c_count > 0 )  
            {  
                char* mem = reinterpret_cast<char*>§;  
                int index = (mem - c_buffer) / sizeof(Test);  
                int flag = (mem - c_buffer) % sizeof(Test);  
                  
                if( (flag == 0) && (0 <= index) && (index < c_count) )  
                {  
                    c_map[index] = 0;  
                      
                    cout << “succeed to free memory: " << p << endl;  
                }  
            }  
            else  
            {  
                free§;  
            }  
        }  
    }  
};  
  
unsigned int Test::c_count = 0;  
char* Test::c_buffer = NULL;  
char* Test::c_map = NULL;  
  
int main(int argc, char argv[])  
{  
    char buffer[12] = {0};  
      
    Test::SetMemorySource(buffer, sizeof(buffer));  
      
    cout << "===== Test Single Object ===" << endl;  
       
    Test
 pt = new Test;  
      
    delete pt;  
      
    cout << "=== Test Object Array =====” << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << “pa[” << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}

3、重載new[] / delete[]

new[] / delete[]與new / delete完全不同

-動態對象數組創建通過new[]完成

-動態對象數組的銷燬通過delete[]完成

-new[] / delete[]能夠被重載,進而改變內存管理方式

new[] / delete[]的重載方式

// static member function
void* operator new[] (unsigned int size)
{
return malloc(size);
}
// static member function
void operator delete[] (void* p)
{
free§;
}
注意事項

-new[]實際需要返回的內存空間可能比期望的要多

-對象數組佔用的內存中需要保存數組信息

-數組信息用於確定構造函數和析構函數的調用次數

編程實驗

動態數組的內存管理    test.cpp

#include    
#include   
  
using namespace std;  
  
class Test  
{  
    int m_value;  
public:  
    Test()  
    {  
        m_value = 0;  
    }  
      
    ~Test()  
    {  
    }  
      
    void* operator new (unsigned int size)  
    {  
        cout << "operator new: " << size << endl;  
          
        return malloc(size);  
    }  
      
    void operator delete (void* p)  
    {  
        cout << "operator delete: " << p << endl;  
          
        free§;  
    }  
      
    void* operator new[] (unsigned int size)  
    {  
        cout << "operator new[]: " << size << endl;  
          
        return malloc(size);  
    }  
      
    void operator delete[] (void* p)  
    {  
        cout << "operator delete[]: " << p << endl;  
          
        free§;  
    }  
};  
  
int main(int argc, char argv[])  
{  
    Test
 pt = NULL;  
      
    pt = new Test;  
      
    delete pt;  
      
    pt = new Test[5];  
      
    delete[] pt;  
      
    return 0;  
}

多四個字節,用於保存數組的大小信息

4、動態內存申請的結果

1、動態內存申請的結果

malloc函數申請失敗時返回NULL值

new關鍵字申請失敗時(根據編譯器的不同)

• 返回NULL值 (古老C++編譯器兼容malloc)

• 拋出std::bad_alloc異常 (現代C++編譯器)

編譯器的不同導致new內存申請失敗時的行爲不同。。。

那麼new語句中的異常是如何拋出的?

new 關鍵字在C++規範中的標準行爲(但編譯器不一定遵循)

- 在堆空間申請足夠大的內存

• 成功: 在獲取的空間中調用構造函數創建對象 ,返回對象的地址

• 失敗: 拋出std::bad_alloc異常

- new在分配內存不足時

• 如果空間不足,會調用全局的new_handler()函數

• new_handler()函數中拋出std::bad_alloc異常

- 可以自定義new _handler()函數

• 處理默認的new內存分配失敗的情況

typedef void (*new_handler)();

new_handler set_new_handler (new_handler new_p) throw(); // C++98
new_handler set_new_handler (new_handler new_p) noexcept; // C++11
new_handler()的定義和使用

void my_new_handler()
{
cout << “No enough memory” << endl;

exit(1);

}

int main()
{
set_new_handler(my_new_handler);

// ...

return 0;

}
2、跨編譯器統一new的行爲

問題 :如何跨編譯器統一new的行爲, 提高代碼移植性?

解決方案 (3種)

-全局範圍(不推薦)

☛ 重新定義new / delete的實現,不拋出任何異常

☛ 自定義new_handler()函數,不拋出任何異常 (後面分析VS中new實現發現此方法對VS有效)

-類層次範圍

☛ 重載new / delete , 不拋出任何異常

-單次動態內存分配

☛ 使用nothrow參數,指明new不拋出異常

編程實驗

動態內存申請    E3-1.cpp

#include
#include
#include
#include

using namespace std;

class Test
{
int m_value;
public:
Test()
{
cout << “Test()” << endl;

    m_value = 0;
}

~Test()
{
    cout << "~Test()" << endl;  
}

void* operator new (unsigned int size) throw()
{
    cout << "operator new: " << size << endl;
    
    // return malloc(size);
    
    return NULL;  // 內存申請失敗
}

void operator delete (void* p)
{
    cout << "operator delete: " << p << endl;
    
    free(p);
}

void* operator new[] (unsigned int size) throw()
{
    cout << "operator new[]: " << size << endl;
    
    // return malloc(size);
    
    return NULL;
}

void operator delete[] (void* p)
{
    cout << "operator delete[]: " << p << endl;
    
    free(p);
}

};

void my_new_handler()
{
cout << “void my_new_handler()” << endl;
}

void ex_func_1()
{
new_handler func = set_new_handler(my_new_handler); // 返回原來的new_handler處理函數

try
{
    cout << "func = " << func << endl;   // 若默認沒有處理函數func爲NULL
    
    if( func )
    {
        func(); // 若默認情況有處理函數,會拋出bad_alloc異常
    }
}
catch(const bad_alloc&)
{
    cout << "catch(const bad_alloc&)" << endl;
}

}

void ex_func_2()
{
Test* pt = new Test();

cout << "pt = " << pt << endl;

delete pt;

pt = new Test[5];

cout << "pt = " << pt << endl;

delete[] pt; 

}

void ex_func_3()
{
int* p = new(nothrow) int[10]; // 不管成功與否都不拋出異常,失敗返回NULL

// ... ...

delete[] p; 

int bb[2] = {0};

struct ST
{
    int x;
    int y;
};

ST* pt = new(bb) ST(); // 將ST的對象創建到bb棧空間裏

pt->x = 1;
pt->y = 2;

cout << bb[0] << endl;
cout << bb[1] << endl;

pt->~ST();  // 需要手工調用析構函數

}

int main(int argc, char *argv[])
{
// ex_func_1();
// ex_func_2();
// ex_func_3();

return 0;

}

① 調用ex_fun_1()函數

VS2015

VS2015和g++打印結果都爲0,說明默認沒有全局的new_handler處理函數

Borland C++ 5.5.1(標準C++)

遵循標準C++的BCC編譯器有默認new_handler處理函數,在這個函數中會拋出bad_alloc異常

② 調用ex_fun_2()函數

在重載new時若不加上throw()

- 在g++下,new的重載返回NULL時,在NULL地址處調用了構造函數創建對象,操作0地址,段錯誤

- 在VS2015和BCC下,new的重載返回NULL時,沒有調用構造函數,pt打印0

三款編譯器動態內存申請失敗時的行爲不一致

加上throw統一了編譯器行爲,在new失敗時不拋任何異常,pt都打印0

③ 調用ex_fun_3()函數

實驗結論

-不是所有的編譯器都遵循C++的標準規範

-編譯器可能重定義new的實現,並在實現中拋出bad_alloc異常

(雖然現代編譯器申請失敗時會拋出bad_alloc異常,但不一定是在new_handler裏拋出的

實驗可以看到VS和g++默認沒有設置全局的new_handler函數,只有BCC設置了)

- 編譯器的默認實現中,可能沒有設置全局的new_handler()函數

-對於移植性要求較高的代碼,需要考慮new的具體細節

查看分析VS2010下new的實現文件

xxx\Microsoft Visual Studio 10.0\VC\crt\src\new.cpp

/***
*new.cxx - defines C++ new routine
*

  •   Copyright (c) Microsoft Corporation.  All rights reserved.
    

*Purpose:

  •   Defines C++ new routine.
    

*******************************************************************************/

#ifdef _SYSCRT /* 通過該宏區分兩個版本的new實現 */
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>

void * operator new( size_t cb )
{
void *res;

for (;;) {

    //  allocate memory block
    res = _heap_alloc(cb);   

    //  if successful allocation, return pointer to memory

    if (res)
        break;

    //  call installed new handler    申請失敗,會調用定義好的new_handler函數 ,而這個過程通過_callnewh函數完成
    if (!_callnewh(cb))
        break;

    //  new handler was successful -- try to allocate again
}

RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));  // 調試代碼忽視

return res;

}
#else /* _SYSCRT */

#include
#include

_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

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
static const std::bad_alloc nomem; // 這個實現中只要不成功必然拋異常
_RAISE(nomem);
}

    return (p);
    }

/*

  • Copyright © 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED.
  • Consult your license regarding permissions and restrictions.
    V3.13:0009 /
    #endif /
    _SYSCRT */
    _callnewh

如果沒有設置全局的處理函數或者設置了全局的處理函數但這個處理函數不能使有更多堆空間,返回值爲0

如果沒有設置全局處理函數就拋出bad_alloc異常

5、小結

new / delete的本質爲操作符

可以通過全局函數重載new / delete (不推薦)

可以針對具體的類重載new / delete

new[]  / delete[]與new / delete完全不同

new[]  / delete[]也是可以被重載的操作符

new[] 返回的內存空間可能比期望的要多

不同的編譯器在動態內存分配上的實現細節不同

malloc函數在內存申請失敗時返回NULL值

new關鍵字在內存申請失敗時

-可能返回NULL值

-可能拋出bad_alloc異常 
————————————————
版權聲明:本文爲CSDN博主「洋蔥汪」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_39654127/article/details/79870658

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章