@TOC


📝new和delete操作自定義類型

我們先看mallocfree,調試可以發現並不會調用析構函數

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{

	A* p1 = (A*)malloc(sizeof(A));
	free(p1);
	
	return 0;
}

C++內存管理的機制_ci

再看newdelete

A* p2 = new A(1);
delete p2;

C++內存管理的機制_ci_02

總結: new/deletemalloc/free最大區別是 new/delete對於【自定義類型】除了開空間還會調用構造函數和析構函數

而對於內置類型幾乎是一樣的

int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;

這是彙編一覽圖: 此時多出來了一個operator new這是什麼,為什麼new會去調用operator new

00482C36    call   operator new(048114Ah )

C++內存管理的機制_構造函數_03

🌠 operator new與operator delete函數

🌉operator new與operator delete函數

newdelete是用户進行動態內存申請和釋放的操作符,operator newoperator delete是系統提供的全局函數,new在底層調用operator new全局函數來申請空間,delete在底層通過operator delete全局函數來釋放空間

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
			// 如果申請內存失敗了,這裏會拋出bad_alloc 類型異常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

operator new:該函數實際通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間失敗,嘗試執行空 間不足應對措施,如果改應對措施用户設置了,則繼續申請,否則拋異常。

// report no memory
// 如果申請內存失敗了,這裏會拋出bad_alloc 類型異常
static const std::bad_alloc nomem;

operator delete: 該函數最終是通過free來釋放空間的

void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}

free的實現

#define free(p) _free_dbg(p, _NORMAL_BLOCK)

如拋異常例子:

double Division(int a, int b)
{
	// 當b == 0時拋出異常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

void Func()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

int main()
{
	try 
	{
		Func();
	}
	catch(const char* errmsg)
	{
	//當 Division(len, time) 函數拋出這種異常時,異常對象會被賦值給 errmsg 變量。
//然後,這個 catch 塊會輸出 errmsg 的內容,即 "Division by zero condition!"
		cout << errmsg << endl;
	}
	catch (...)
	{
	//這個 catch 塊用於捕獲任何其他類型的未知異常。
//當 try 塊中發生任何其他類型的異常時,這個 catch 塊會被執行。
//它會輸出 "unkown exception"。
		cout << "unkown exception" << endl;
	}
	
	return 0;
}

當你輸入兩個數讓b == 0時程序拋異常,拋出"Division by zero condition!"他不會再回到Func()函數中的cout << Division(len, time) << endl;而是會跳到catch(const char* errmsg)中,異常對象會被賦值給 errmsg 變量。然後,這個 catch 塊會輸出 errmsg 的內容,即 "Division by zero condition!",程序結束。

總結: 通過上述兩個全局函數的實現知道,operator new 實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執行用户提供的空間不足應對措施,如果用户提供該措施就繼續申請,否則就拋異常。operator delete 最終是通過free來釋放空間的。

🌠new和delete的實現原理

🌉內置類型

如果申請的是內置類型的空間,newmallocdeletefree基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]delete[]申請的是連續空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL

🌉自定義類型

new的原理

  1. 調用operator new函數申請空間
  2. 在申請的空間上執行構造函數,完成對象的構造 delete的原理
  3. 在空間上執行析構函數,完成對象中資源的清理工作
  4. 調用operator delete函數釋放對象的空間
class Stack 
{
public:
    // 構造函數
    Stack(int initialCapacity = 10) 
        : _a(new int[initialCapacity])
        , _top(0)
        , _capacity(initialCapacity) 
    {}

    // 析構函數
    ~Stack() 
    {
        delete[] _a;
    }

    // 壓入元素
    void push(int value) 
    {
        // 如果棧已滿,需要擴容
        if (_top == _capacity) 
        {
            resize(2 * _capacity);
        }
        // 將元素壓入棧頂
        _a[_top++] = value;
    }

    // 彈出棧頂元素
    int pop() 
    {
        // 如果棧為空,拋出異常
        if (_top == 0) 
        {
            throw runtime_error("Stack is empty");
        }
        // 彈出棧頂元素並返回
        return _a[--_top];
    }

    // 判斷棧是否為空
    bool empty() const 
    {
        return _top == 0;
    }

    // 返回棧中元素的個數
    int size() const 
    {
        return _top;
    }

private:
    // 擴容函數,分配新的數組空間並移動元素
    void resize(int newCapacity) 
    {
        // 分配新的數組空間
        int* newArray = new int[newCapacity];
        // 使用 std::move 將原數組中的元素移動到新數組中
        move(_a, _a + _top, newArray);
        // 釋放原數組空間
        delete[] _a;
        // 更新數組指針和容量
        _a = newArray;
        _capacity = newCapacity;
    }

    // 存儲元素的數組指針
    int* _a;
    // 棧頂元素的索引
    int _top;
    // 數組的容量
    int _capacity;
};

int main() 
{
    // 創建一個棧
    Stack* p3 = new Stack;
    // 釋放棧的內存
    delete p3;

    //// 壓入三個元素
    //p3->push(1);
    //p3->push(2);
    //p3->push(3);
    //// 依次彈出並打印元素
    //while (!p3->empty()) 
    //{
    //    cout << p3->pop() << " ";
    //}
    //cout << endl;
    return 0;
}

C++內存管理的機制_構造函數_04

先析構_a指向的空間,再釋放p3指向的空間

new T[N]的原理

  1. 調用operator new[]函數,在operator new[]中實際調用operator new函數完成N個對象空間的申 請
  2. 在申請的空間上執行N次構造函數

delete[]的原理 3. 在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理 4. 調用operator delete[]釋放空間,實際在operator delete[]中調用operator delete來釋放空間

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
	A* p1 = new A;
	A* p2 = new A[10];

	delete p1;
	delete[] p2;

	return 0;
}

C++內存管理的機制_構造函數_05

A類只有一個4字節大小的int成員變量。那麼,創建一個A類型的對象應該需要4字節的內存空間。A* p2 = new A[10];這我們動態創建了一個包含10個A對象的數組。10 * 4 = 40 bytes,為什麼是44bite呢?

在動態分配數組內存時,編譯器通常會在實際的數組內存之前分配一些額外的空間,用於存儲數組的元素個數等信息。這樣做的目的是為了在執行delete[]操作時,能夠正確地調用所有元素的析構函數。

總結:都開4byte存儲對象個數,方便delete[]時,知道有多少個對象,要調用多少次析構函數

C++內存管理的機制_數組_06

內置類型就沒有額外開空間: 因為,內置類型已經固定好,無需調用析構函數

int* p3 = new int[10];
delete[] p3;

C++內存管理的機制_數組_07

🌠定位new表達式(placement-new)

定位new表達式是在已分配的原始內存空間中調用構造函數初始化一個對象。使用格式:

new (place_address) type或者new (place_address) type(initializer-list)
place_address必須是一個指針,initializer-list是類型的初始化列表

使用場景:定位new表達式在實際中一般是配合內存池使用。因為內存池分配出的內存沒有初始化,所以如果是自定義類型的對象,需要使用new的定義表達式進行顯示調構造函數進行初始化。

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

// 定位new/replacement new
int main()
{
	// p1現在指向的只不過是與A對象相同大小的一段空間,還不能算是一個對象,因為構造函數沒有執行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A; // 注意:如果A類的構造函數有參數時,此處需要傳參
	p1->~A();
	free(p1);

	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);

	return 0;
}
A* p1 = (A*)malloc(sizeof(A));

使用malloc()函數分配了一塊與A類對象大小相同的內存空間,但此時p1指向的只是一塊內存空間,還不是一個真正的A對象,因為A的構造函數還沒有被調用。

new(p1)A;

使用"定位new"的語法在已分配的內存空間上構造一個A對象。如果A類的構造函數有參數,需要在這裏傳入參數,例如new(p2)A(10);

p1->~A();

顯式地調用A對象的析構函數,釋放對象佔用的資源。

free(p1);

free()函數釋放之前使用malloc()分配的內存空間。

不要錯配使用delete和free,一定匹配使用,否則結果是不確定