目錄
c++的動態記憶體管理方式和c語言不一樣,在c++中使用new和delete來替換c語言中的malloc和free。這裡有幾個點不一樣,
1、new和delete是操作符,malloc和free是函式(我的理解是c++將new和delete約定為操作符而已,new和delete操作符過載函式本質上還是函式)
2、c++有了類的概念,類物件的初始化除了要分配記憶體,還需要對記憶體進行初始化!所以,c++必須引入一種新的記憶體分配方式,既可以像malloc一樣開闢記憶體,還要能夠呼叫類物件的建構函式(delete的引入同理)。
3、new和delete是c++完全新增的記憶體操程式設計客棧作符,他們和new和delete也是有不一樣的地方。
下面,咱們來一一講解⬇
既然new和delete都是操作符,咱們可以對new和delete進行過載;當你再使用new和delete操作記憶體時,編譯器就會呼叫到咱們自己過載的new/delete全域性函式了。(如對操作符過載不了解的,請自行補充知識)
void* operator new(size_t size)
std::cout << "new memory size = " << size << " address = " << ptr << std::endl;
程式設計客棧return ptr;
}void* operator new(size_t size)
std::cout << "new memory size = " << size << " address = " << ptr << std::endl;
return ptr;
}void operator delete(void* ptr)
void operator delete(void* ptr)
此時,再使用 int* p = new int ; 開闢記憶體,那麼,c++編譯器會自動鏈結到我們剛才的操作符過載函式 void* operator new(size_t size) ,至於編譯器是怎麼將 int* p = new int ; 解析成 void* operator new(size_t size) 函式的,咱們不關心,咱們只要知道編譯器做了這樣一www.cppcns.com層**解析轉換即可。並且,轉換的過程中會將需要開闢的記憶體大小當作形式引數傳遞過去!
這裡還有一點需要注意:函式的返回值是void*,而咱們new操作接收的返回值為int*,這裡其實也是編譯在底層做了一層轉換!(非常重要,new 的返回值和 operator new 函式的返回並不完全相同,包括型別和位址)
在開始驗證new和delete之前,咱們先使用以下**:
#include
#include
#include
using namespace std;
//此處新增前面一步過載new和delete的**
class master
~master()
public:
int c;
};int main()
; delete p;
return 0;
}推薦乙個**除錯cpp的**:compiler explorer (godbolt.org)
master * p = new master{};這一行的彙編資訊如下:
這裡有3個點要注意,
www.cppcns.com
那麼,delete操作的彙編資訊應該和new剛好相反,這個可以自行測試!
下面將咱們過載的operator方法新增進去,然後執行,output日誌資訊如下:
new memory size = 4 address = 0x2154eb0
constructor ---- 0x2154eb0
destructor ---- 0x2154eb0
delete 0x2154eb0
通過日誌,咱們也能夠得出這樣的結論:
new: 先malloc記憶體,然後執行建構函式 delete:先執行析構函式,然後free記憶體
前面咱們說了new/delete與new/delete有一些不同,那我們先看看日誌資訊,請新增以下測試**:
int main()
; //測試new
master * parray = new master[5];
printf("parray = %p\n", parray);
printf("parray[0] = %p\n", &parray[0]);
printf("parray[1] = %p\n", &parray[1]);
// delete parray;
// delete p;
return 0;
}///
output:---------------------------------------begin
new memory size = 4 address = 0x565422925eb0
constructor ---- 0x565422925eb0
new memory size = 28 address = 0x5654229262e0
constructor ---- 0x5654229262e8
constructor ---- 0x5654229262ec
constructor ---- 0x5654229262f0
constructor ---- 0x5654229262f4
constructor ---- 0x5654229262f8
parray = 0x5654229262e8
parray[0] = 0x5654229262e8
parray[1] = 0x5654229262ec
output:----------------------------------------end
重點來了,malloc返回的記憶體位址(0x5654229262e0)和new返回的位址(0x5654229262e8)並不一致,而且申請的記憶體大小並不是master類的大小 4*5=20,而是28!!
通過彙編**,我們看到這裡確實偏移了8個位元組!其他的操作與new沒有多大差別,都是先呼叫operator new,然後再迴圈呼叫陣列中的每個物件的建構函式!
通過output日誌和彙編分析,可以得到我們前面講到的結論:(非常重要,new 的返回值和 operator new 函式的返回並不完全相同,包括型別和位址)!
我們呼叫 delete parray; 刪除的位址為 0x5654229262e8,而真實觸發 operator delete 的形參為 0x5654229262e0, 詳細日誌資訊如下:
也就是operator new 和 operator delete 的操作的記憶體是真實位址,並且是正確的!為什麼會這樣?就是因為c++編譯器在底層做了一次轉換!
這個問題也很好理解,如果編譯器沒有這樣一層邏輯轉換,當你delete乙個陣列時,編譯器要怎麼操作呢?他怎麼知道陣列的長度然後遍歷去呼叫析構函式呢?所以,這樣一層邏輯轉換有了存在的意義,這也體現在 new 陣列時為什麼大小並不是 = 物件大小*陣列長度,而是多了8個位元組!
好了嘛,那多出來的8位元組是不是就是陣列長度呢?咱們可以把這塊記憶體資訊輸出!新增以下**:
unsigned long* psize = (unsigned long*)parray - 1;//偏移8位元組
printf("p = %p\n", psize);
printf("sizeof parray = %d\n", *psize);//輸出該位址上的記憶體資料
日誌輸出:
偏移8位元組之後就是咱們 operator new 函式分配的真實的記憶體位址,然後咱們輸出該位址8位元組空間的資料資訊為5,這就是申請的資料空間的大小!
也就是有了這個多餘的8位元組(在32位系統上為4位元組),new 儲存陣列長度到該記憶體, delete 時就能夠從該記憶體讀取陣列長度,並遍歷陣列去呼叫析構函式啦!
本文標題: 深入理解c語言的new和delete
本文位址:
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...
C語言指標深入理解
前幾天看了乙個程式,裡面一段關於指標的 讓我非常糾結,看了很久才看懂,在這裡將將其記錄下來,希望能對大家有一定的幫助,先看示例程式 編譯器gcc include include include typedef struct list node list node,list,plist node st...