1. 程式人生 > >C++回顧——new和delete

C++回顧——new和delete

一、物件的建立
當建立一個C++物件時,會發生兩件事:
1)為物件分配記憶體;
2)呼叫建構函式來初始化那個記憶體(C++強迫這樣做是因為未初始化的物件是程式出錯的主要原因)

C++把建立一個物件所需要的所有動作都結合在一個稱為new的運算子裡(分配記憶體並呼叫建構函式),預設的new還進行檢測以確信在傳遞地址給建構函式之前記憶體分配是成功的,所以不必顯示地確定呼叫是否成功。

delete只用於刪除由new建立的物件,delete表示式首先呼叫解構函式,然後釋放記憶體(經常是呼叫free())。正如new表示式返回一個指向物件的指標一樣,delete表示式需要一個物件的地址。如果正在刪除的物件的指標是0,將不發生任何事情(故經常在刪除指標後立即把指標賦值為0以免對它刪除兩次,對一個物件刪除兩次可能會產生某些問題)。如果delete一個void指標,唯一發生的事就是釋放記憶體(因為既沒有型別資訊也沒有辦法使得編譯器知道要呼叫哪個解構函式),這將可能成為一個程式錯誤。如果在程式中發現記憶體丟失的情況,那麼就搜尋所有的delete語句並檢查被刪除指標的型別。如果是void*型別,則可能發現了引起記憶體丟失的某個因素(C++還有很多其他的引起記憶體丟失的因素)。

二、用於陣列的new和delete
MyType * fp = new MyType[10];
在堆上為10個MyType物件分配了足夠的記憶體併為每一個物件呼叫了建構函式。fp實際上是一個數組的起始地址,它可以用類似fp[3]的形式來選擇陣列的元素。
銷燬陣列時,使用delete []fp;空的方括號告訴編譯器產生程式碼,該程式碼的任務是將從陣列建立時存放在某處的物件數量取回,併為陣列的所有物件呼叫解構函式。
如果使用delete fp;另外9個解構函式沒有呼叫,適當數量的儲存單元會被釋放,但是,由於它們被分配在一個整塊的記憶體中,所以,整個記憶體塊的大小被分配程式在某處中斷了。

三、耗盡記憶體
當operator new()找不到足夠大的連續記憶體塊來安排物件時,一個稱為new-handler的特殊函式將會被呼叫,首先檢查指向函式的指標,如果指標非0,那麼它指向的函式將被呼叫。new-handler的預設動作是產生一個異常,如果已經過載了operator new()則new-handle將不會按預設呼叫,如果仍想呼叫new-handler,則需要在過載了的operator new()的程式碼中加上做這些工作的程式碼。
如果想代替new-handler,我們在程式中至少要用“記憶體已耗盡”的資訊代替new-handler,並異常中斷程式。通過包含new.h來替換new-handler,然後以想裝入的函式地址為引數呼叫set_new_handler()函式(函式必須不帶引數且其返回值為void)。

四、過載new和delete
使用了new和delete的記憶體分配系統是為通用目的而設計的,但是有時它不能滿足需要(比如要建立和銷燬一個特定的類的非常多的物件以至於這個運算變成了速度的瓶頸;分配不同大小的記憶體可能會產生很多碎片,以至於很快用完記憶體,通過為特定的類建立自己的記憶體分配器,可以確保這種情況不會發生),此時就需要過載new和delete。
過載operator new()和operator delete()時,只是改變了原有的記憶體分配方法,編譯器將用過載的new代替預設的版本去分配記憶體,然後為那個記憶體呼叫建構函式。當過載operator new()時,也可以替換它用完記憶體時的行為,所以必須在operator new()裡決定做什麼:返回0、寫一個呼叫new-handler的迴圈、再試著分配或產生一個bad_alloc的異常資訊。
1、過載全域性new和delete
當全域性版本的new和delete不能滿足整個系統時,對其過載是很極端的方法。如果過載全域性版本,就使預設版本完全不能被訪問,甚至在這個重新定義裡也不能呼叫它們。
過載的new必須有一個size_t引數,它由編譯器產生並傳遞給我們,它是要分配記憶體的物件的長度。必須返回一個指向等於這個長度(或大於這個長度)的物件的指標,如果沒有找到儲存單元(在這種情況下,建構函式不被呼叫),則返回一個0,呼叫new-handler或產生一個異常資訊,通知這裡存在問題。
operator new()的返回值是一個void*,而不是指向任何特定型別的指標,它僅僅是分配記憶體,直到建構函式呼叫才完成物件的建立(這是編譯器確保做的動作)。
operator delete()的引數是一個指向由operator new()分配的記憶體的void*(因為它是在呼叫解構函式後得到的指標)。解構函式從儲存單元裡移去物件,operator delete()的返回型別是void。
簡單示例如下:

void* operator new(size_t sz) {
    printf(" operator new:%d bytes\n", sz);
    void* m = malloc(sz);
    if(!m)
    {
        puts("out of memory");
    }
    return m;
}

void operator delete(void* m) {
    puts("operator delete");
    free(m);
}

這裡的記憶體分配使用了標準C庫函式(可能預設的new和delete也使用)。

2、對於一個類過載new和delete
為一個類過載new和delete時,儘管不顯示使用static,但實際上仍是建立static成員函式。當編譯器看到使用new建立自己定義的類物件時,它選擇成員版本的operator new()。
簡單示例如下:

class Framis {
    enum { sz = 10 };
    char c[sz]; // to take up space, not used
    static unsigned char pool[];
    static bool alloc_map[];

public:
    enum { psize = 100 }; // framis allowed
    Framis() { printf("Framis()\n"); };
    ~Framis() { printf("~Framis()\n"); }
    void * operator new(size_t) throw(bad_alloc);
    void operator delete(void*);
};

unsigned char Framis::pool[psize * sizeof(Framis)];
bool Framis::alloc_map[psize] = { false };

void * Framis::operator new(size_t) throw(bad_alloc) {
    for (int i = 0; i < psize; ++i)
    {
        if ( !alloc_map[i] )
        {
            printf("using block %d", i);
            alloc_map[i] = true; // mark it used
            return pool + (i * sizeof(Framis));
        }
    }

    printf("out of memory\n");
    throw bad_alloc();
}

void Framis::operator delete(void* m) {
    if ( !m )
    {
        return;
    }

    // assume it was created in the pool
    // calculate which block number it is
    unsigned long block = (unsigned long)m - (unsigned long)pool;
    block /= sizeof(Framis);
    printf("free block %d\n", block);
    alloc_map[block] = false;
}

如果使用繼承,該分配方案不能自動繼承使用。

3、為陣列過載new和delete
如果為一個類過載了operator new()和operator delete(),無論何時建立這個類的一個物件都將呼叫這些運算子。但是如果要建立這個類的一個物件陣列時,全域性operator new()就會被立即呼叫,用來為這個陣列分配足夠的記憶體。對此,可以通過為這個類過載運算子的陣列版本operator new[]和operator delete[],來控制物件陣列的記憶體分配。
簡單示例如下:

class Widget{
    enum { sz = 10 };;
    int i[sz];

public:
    Widget() { printf("Widget()\n"); }
    ~Widget() { printf("~Widget()\n"); }

    void * operator new(size_t sz) {
        printf("Widget::new %d bytes\n", sz);
        return ::new char[sz];
    }

    void operator delete(void * p) {
        printf("Widget::delete\n");
        ::delete []p;
    }

    void * operator new[](size_t sz) {
        printf("Widget::new[] %d bytes\n", sz);
        return ::new char[sz];
    }

    void operator delete[](void * p) {
        printf("Widget::delete[]\n");
        ::delete []p;
    }
};

可以看到,語法上處理多一對括號外,陣列版本的new和delete與單個物件版本的是一樣的。

過載operator new()還有其他兩個不常見的用途:
1)我們也許會想在記憶體的指定位置上放置一個物件(這對於面向硬體的內嵌系統特別重要,一個物件可能和一個特定的硬體是同義的);
2)我們也許在呼叫new時,能夠選擇不同的記憶體分配方案。
這兩個特性可以用相同的機制實現:過載的operator new()可以帶一個或多個引數。第一個引數總是物件的長度,它在內部計算出來並由編譯器傳遞給new。但其他的引數可由我們自己定義:一個放置物件的地址、一個是對記憶體分配函式或物件的引用,或其他任何使我們方便的設定。此時銷燬物件時需要顯示地呼叫解構函式。

相關推薦

C++回顧——newdelete

一、物件的建立 當建立一個C++物件時,會發生兩件事: 1)為物件分配記憶體; 2)呼叫建構函式來初始化那個記憶體(C++強迫這樣做是因為未初始化的物件是程式出錯的主要原因) C++把建立一個物件所需要的所有動作都結合在一個稱為new的運算子裡(分配記憶

C++基礎 newdelete

style 析構函數 對比 pan delete 基本數據類型 交叉 del 報錯 1.new delete 的使用 (1)基本數據類型 int *p = new int(10); delete p; int *p = (int *)malloc(sizeof(int))

C++中newdelete之後發生了什麼

眾所周知,如果我們使用new向系統申請了記憶體,我們應該使用指標指向這一塊記憶體,俾能我們使用結束後,通過delete該指標釋放此記憶體資源。 如果理解只達到這種程度,在記憶體管理稍微複雜一點時便一定會束手無策。總有一些事情比其他事情更基本一點,現在我來談談當我們new和delete之後

C++中newdelete的用法

new和delete運算子用於動態分配和撤銷記憶體的運算子 new用法:           1.     開闢單變數地址空間  

C++中newdelete的背後

NewTest!wmain: 00aa1020 56              push    esi 00aa1021 6a04            push    4  00aa1023 e8b4030000      call    NewTest!operator new (00aa13d

C++重寫newdelete,比想像中困難

  關於C++記憶體管理這話題,永遠都不過時。在我剛出道的時候,就已經在考慮怎麼檢測記憶體洩漏(https://www.cnblogs.com/coding-my-life/p/3985164.html)。想用一份簡單的程式碼,並且不太影響執行效率去實現記憶體洩漏檢測,是不太現實的。當時覺得重寫new和del

C++之new delete

new 和 delete要成對使用,且要採取相同形式。 使用new時,會發生2件事,1.記憶體被分配出來;2.針對此記憶體會有一個(更多)建構函式被呼叫; 使用delete,發生2件事,1.針對此記憶體的解構函式被呼叫;2.記憶體被釋放; 舉例: std::string

c++中newdelete的區別

new 和delete 是運算子,而不是函式。 new new 運算子的格式: new 型別[初值]; new int; ///開闢一個存放整數的儲存空間,返回一個指向該儲存空間的地址

c++的newdelete的方法使用以及不用new定義變數

new和delete運算子用於動態分配和撤銷記憶體的運算子 new用法:           1.     開闢單變數地址空間                1)new int;  //開闢一個存放陣列的儲存空間,返回一個指向該儲存空間的地址.int *a = ne

C++:NewDelete的用法

//C++裡 陣列new 和delete問題 //對於陣列new的不同方式 和不同的釋放方法 //---------------------------------------------

C++ 類(newdelete運算子)

文章概述 new和delete運算子 new和delete運算子 a. new/delete 是C++的運算子;類似於malloc/free,程式執行(動態)得開闢記憶體空間(堆); b. new 可以為內建型別的變數開闢空間,陣列變數,類的物件

C++中newdelete

New運算子 1、new表示式: 當我們使用一條new表示式時: string *sp=newstring(“a value”); string *arr=newstring[10]; 實際上執行了三步操作。第一步,new表示式呼叫一個名為operatornew(或者ope

C++分析NewDelete----C語言MallocFree

C++中為什麼不用Malloc和Free,而用New和Delete?   <1> 如圖: 類的物件在堆上分配的時候用malloc和free: 我們觀察發現雖然在堆上申請空間了,但並沒有呼叫

c++中newdelete的預設賦值測試

  為了測試c++中對new操作符的運算規則是否呼叫預設物件的建構函式進行初始化,故寫出如下的測試demo: #include <iostream> using namespace std;

2.16 C++類與newdelete操作符

運行 out 可能 clas cout std 存儲 分配 程序 參考: http://www.weixueyuan.net/view/6347.html 總結:    當我們需要為類對象動態分配存儲空間時,我們應該使用C++語言提供的new與new[]操作符,而不要使用C

c++中的newdelete

程序設計 wan https 解決 運算符 AI 指向 log cnblogs 對於計算機程序設計而言,變量和對象在內存中的分配都是編譯器在編譯程序時安排好的,這帶來了極大的不便,如數組必須大開小用,指針必須指向一個已經存在的變量或對象。對於不能確定需要占用多少內存的情況,

effective c++ 條款16:成對使用newdelete時要采用相同形式

最好 class 你在 pan TE fec IV line PE 記住: 如果你在new表達式中使用[ ],必須在相應的delete表達式中也是用[ ]。如果你在new時不使用[ ],一定不要在delete時使用[ ]。 string* stringPtr1 = n

effective c++條款16:成對使用newdelete時要採取相同形式

下面的程式碼會產生什麼樣的後果? #include <iostream> using namespace std; int main(void) { std::string *StringArray = new std::string[30]; delete StringAr

effective C++筆記--定製newdelete(二)

文章目錄 編寫new和delete時需固守常規 寫了placement new也要寫 placement delete 編寫new和delete時需固守常規 . 在編寫自己的operator new和operator delete時,需要

C++ Memory System Part1: newdelete

part 也有 其中 oid 事情 oca ddr temp 高級工程師 在深入探索自定義內存系統之前,我們需要了解一些基礎的背景知識,這些知識點是我們接下裏自定義內存系統的基礎。所以第一部分,讓我們來一起深入了解一下C++的new和delete家族,這其中有很多令人吃驚的