C++內存分配與釋放


C++內存分配與釋放

1. new 運算符 與 operator new
一條 new 表達式語句( new Type; )中的 new 是指 new 運算符.
operator new 是定義在 #include <new> 中聲明的一系列全局函數, 其中部分全局函數可被重寫, 或在自定義類型定義為成員函數, 這樣該類或其子類將使用成員函數的版本進行內存分配.
new 和 operator 對應用程序至關重要, 一旦應用程序定義了全局版本的 operator new/delete, 應用程序就負擔起了分配對態內存的職責, 必須保證這兩個函數的完全正確.

2. new 與 delete 運算符
對 new Klass 表達式, 編譯器執行3個過程:
(1) 調用 operator new 或 operator new[] 函數分配一塊足夠大的,原始,未命名的內存以存儲該類型的對象或對象的數組.
編譯器查找的順序為:首先在類及其基類中查找,其次全局作用域內查找,如果沒有找到使用標准庫定義的版本.
(2) 調用相應的構造函數, 構造這些對象, 並為其傳入初始值.
(3) 對象被分配了空間並構造完成后, 返回一個指向該對象或對象數組的指針.
delete p 執行相反的過程:
(1) 調用對象或對象數組的每一個元素調用析構函數.
如果析構函數為虛函數(頂層基類析構函數為虛函數則該類析構函數也為虛函數), 調用對象實際類型的析構函數.
如果為對象數組, 則從后往前調用析構函數.
(2) 調用 operator delete 或 operator delete[] 函數釋放內存.
重寫 new 與 delete 實際是重寫了 operator new 和 operator delete 函數, 應用程序無法改變 new 和 delete 運算符的行為!
顯示的調用 operator 版本的函數與使用 new 或 delete 表達式形式, 形式無多大區別, 但他們之間的差異驚人, 需要仔細甄別.
如果想把內存分配和對象構造分離開來, 可以使用 placement new 形式(或 allocator 類).
char buf[100]; // 分配內存
Object* p = new (buf) Object{1}; // 構造對象
p->~Object(); // 析構對象

3. 編譯器定義的不同版本的 operator new/delete 函數的功能
void* operator new(size_t) bad_alloc;
void* operator new[](size_t) bad_alloc;
void operator delete(void*) noexcept;
void operator delete[](void*) noexcept;
void* operator new(size_t, nothrow_t) noexcept;
void* operator new[](size_t, nothrow_t) noexcept;
void operator delete(void*, nothrow_t) noexcept;
void operator delete[](void*, nothrow_t) noexcept;
void* operator new(size_t, void* p) noexcept { return p; }
void* operator new[](size_t, void* p) noexcept { return p; }
void operator delete(void*, void*) noexcept {}
void operator delete[](void*, void*) noexcept {}
(1) operator new 默認版本在分配內存失敗時拋出 bad_alloc, 其余的函數不拋出異常.
(2)第(1)-(8)個函數可以重寫.
(3)第(9)-(12)函數的全局版本不能被重新定義(類成員版本無此限制), 實際上它們什么也不干, operator new 也只是簡單的返回傳入的地址(注意, 不分配內存).
其中p指向的內存, 可以為任意地址, 包括棧上分配的內存, 只要其大小足夠容納對象. 這樣應用程序就可以在預先分配的內存上構造對象.
常見的 new 表達式使用形式約有不同, 如: Object* p = new (buf) Object{1}; // 以 buf 指定的地址構造一個 Object 對象, 其構造函數參數為 1.
如果使用此方式, 構造了一個對象, 在內存銷毀前, 需要手動調用對象的析構函數銷毀對象, 如 p.~Object();
(4)重寫的 operator new 函數必須返回 void*, 並且第一個參數必須為 size_t 類型, 且該參數不能有默認實參. 重載的版本可以提供其它額外的參數.
(5)為對象數組分配空間時使用 operator new[], 傳入第一個參數為數組所有元素所需的空間.
(6) operator new 在內存不足時會調用 new_handler 函數, 並要求其釋放一部分內存, 只有在 new_handler 為空時才會拋出異常.
可以調有標准庫函數 set_new_handler 重新設置 new_handler 為自已定義的版本.
(7)operator new 的 nothrow_t 版本並不能保證 new 不拋出異常. 舉個例子:
Object* p = new (nothrow) Object{}; 雖然指定了為 Object 對象分配內存時不拋出異常, 但 Object 在構造其成員對象的過程中, 如果內存不足, 仍然會拋出 bad_alloc.
(8)如果調用 operator new 時, 要求分配的內存大小為 0 字節, 也會返回一個合法的地址.
(9)delete 刪除 nullptr 永遠是安全的行為.
(10)重寫 operator new 時也要重寫對應的版本的 operator delete.

直接使用內存示例(僅用作演示函數功能, 實際項目中不要使用):
const size_t BUF_SZ = 100;
void* pbuf = operator new(BUF_SZ); // 分配100個字節的內存, 此時無對象, 當然也就不會調用構造函數
memset(pbuf, '1', BUF_SZ);
char* pc = static_cast<char*>(pbuf);
pc[BUF_SZ - 1] = '\0';
cout << "kao:" << pc << endl;
operator delete(pbuf); // 釋放內存, 不能直接使用 delete pbuf;

4. 類成員函數 operator new/delete(數組版本同理,不贅述)
(1) 重寫這些函數與普通的 operator 系列函數(如 operator <)意義完全不同, 需要區別對待.
(2) 類成員函數的 operator new/delete 必須為 static 函數, 並且可以不使用 static 聲明.
(3) 和其它成員函數一樣, 受訪問權限限定符限制, 例如 operator new 函數在類定義中聲明為 private, 則該類及其子類都不能使用 new 分配對象.
(4) 一旦一個類型中定義了一個 operator new 版本, 則需要同時實現其它 #include <new> 中聲明的其它版本, 否則將不可以使用, 這和其它函數的重載類似.
(5) 自定義類型中重載的 operator new 函數, 可以添加自定義參數. 但第一個參數必須為 size_t 類型, 並且返回類型必須為 void*.
(6) 如果定義類型自已的 operator new 或 operator delete, 可以使用作用局運算符調用全局函數的版本. 如 ::new KlassA;
(7) 當定義 operator delete 或 operator delete [] 時, 第二個形參可以為size_t類型的參數, 以提供第1形參所指對象的字節數.
此形參用於刪除繼承體系中的對象, 如果其類對象中有一個虛函數, 其大小將為指針所指對象的動態類型的大小. (對單個對象調用仍使用 delete p;)

5. 使用 allocator 類分配內存.
allocator 類定義在頭文件 memory 中, 使用 allocator 可將內存分配和構造過程分離開來.
allocator 類是一個模板類, 可以在頭文件中看到其完整定義. 它分配的內存是原始的, 未構造的, 標准庫模板類采了此方法分配內存.
主要的成員函數:
allocator<t> alloc; // 定義一個名為 alloc 的 allocator 對象, 它可以為類型為 T 的對象分配內存
T* allocate(size_t n); 分配一段原始,未構造的內存, 保存 n 個 T 類型的對象
void construct(T* p, Args&&... args); args 被用來傳遞給構造函數, 用來在 p 指向的內存中構造一個對象
void destroy(T* p); 析構一個對象, 即調用對對象 p 調用 T 的析構函數, p 必須是已構造的對象
void deallocate(T* p, size_t n); p 必須是 allocate 返回的地址, 且 n 為 allocate 分配時指定的大小. 調用此函數之前, 應用程序必須確保對其中的每一個已初始化了的對象都調用了 destroy 函數
未構造對象前使用對象, 其行為是未定義的. 當銷毀一個對象后, 可以在不釋放內存前重復使用該內存.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM