摘要:“new”是C++的一個關鍵字,同時也是操作符。關於new的話題非常多,因為它確實比較復雜,也非常神秘。
本文分享自華為雲社區《如何編寫高效、優雅、可信代碼系列(2)——你真的會用new嗎》,原文作者:我是一顆大西瓜 。
C++內存管理
1. C++內存分配
C++中的程序加載到內存后按照代碼區、數據區、堆區、棧區進行布局,其中數據區又可以分為自由存儲區、全局/靜態存儲區和常量存儲區,各區所長如下:
- 棧區
函數執行的時候,局部變量的存儲單元都在棧上創建,函數執行結束后存儲單元會自動釋放。棧內存分配運算內置於處理器指令集中,效率高,但分配內存容量有限。 - 堆區
堆就是new出來的內存塊,編譯器不管釋放,由應用程序控制,new對應delete。如果沒釋放掉,程序結束后,操作系統會自動回收。 - 自由存儲區
C中malloc分配的內存塊。用free結束生命周期。 - 全局/靜態存儲區
全局變量和靜態變量被分配到同一塊內存中,定義的時候就會初始化。 - 常量存儲區
比較特殊的存儲區,存放常量,不允許修改。
堆和棧的區別
- 管理方式
棧由編譯器自動管理,堆由程序員控制 - 空間大小
32位系統下,堆內存可以達到4GB,棧有一定的空間大小 - 碎片管理
對於堆,頻繁的new/delete肯定造成內存空間的不連續,產生大量內存碎片降低程序效率;棧由於遵循先進后出的規則,不會產生空隙 - 生長方向
堆是向上生長的,即向着內存地址增加的方向增長;而棧是向着內存地址減小的方向增長的 - 分配方式
堆是動態分配的,棧有動態分配和靜態分配之分:靜態分配由編譯器完成,動態分配由alloca函數完成,即使是動態分配,依然是編譯器自動釋放 - 分配效率
計算機底層提供了棧的支持,分配了專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這決定了棧的效率會比較高。堆則是由C/C++函數庫提供的,機制比較復雜,比如為了分配某個大小的內存需要在堆內存中搜索可用足夠大小的空間,效率比棧要低的多
2. new/delete和new []/delete []
- 回收new分配的單個對象內存空間時用delete,回收用new[]分配的一組對象時用delete[]
- 對於內置類型(int/double/float/char/…),由於new[]申請內存時,編譯器還會悄悄在內存中保存整數,表示指針數組的個數,所以delete/delete[]都可以正確釋放所申請的內存空間
- 建議在調用new時使用的[],那么調用delete也使用[]
3. new的三種形態
- new operator 常用的new,語言函數內建,不能重載。調用過程中實際完成的有三件事:
- 為類型對象分配內存;
- 調用構造函數初始化內存對象;
- 返回對象指針
如果是在堆上建立對象,直接使用new operator。
- operator new 普通操作符,可以重載。如果僅僅是分配內存,那么應該調用operator new,但不負責初始化。系統默認提供的分配器在時間和空間兩方面都存在一些問題:分配器速度較慢,分配小型對象時空間浪費嚴重,重載new/delete有三方面好處:
- 改善效率
- 檢測代碼中的內存錯誤
- 獲得內存使用的統計數據
- C++標准規定,重載的operator new必須是類成員函數或全局函數,全局的operator new重載不應該改變原有簽名,而是直接無縫替換原有版本。全局重載很有侵略性,別人使用你的庫無法使用默認的new,而具體類的重載只會影響本class和其派生類,但是類的operator new函數重載必須聲明為static,因為operator new是在類的具體對象被構建出來之前調用的。
- 為了獲得2和3的優勢,重載的operator new需要如下函數聲明void* operator new(size_t, const char* file, int line);
- placement new 定義在庫<<new>>中。如果想在一塊已經獲得內存里建立對象,那么應該調用placement new。通常情況不建議使用,但在某些對時間要求非常高的應用中可以考慮,因為選擇合適的構造函數完成對象初始化是一個時間相對較長的過程。