C++ 動態實例化(new 和 malloc) malloc / free 工作原理 malloc 是 stdlib.h 庫中的函數,聲明為 void *__cdecl malloc(size_t _Size); 原理: malloc 函數沿空閑鏈表(位於記憶體 堆空間 中)申請一塊滿足需求的記憶體塊, ...
C++ 動態實例化(new 和 malloc)
malloc / free
工作原理
malloc
是 stdlib.h
庫中的函數,聲明為 void *__cdecl malloc(size_t _Size);
-
原理:
malloc
函數沿空閑鏈表(位於記憶體 堆空間 中)申請一塊滿足需求的記憶體塊,將所需大小的記憶體塊分配給用戶剩下的返回到鏈表上;並返回指向該記憶體區的首地址的指針,意該指針的類型為
void *
,因此我們需要強制轉換指針類型; -
參數:
_Size
為要申請的空間大小,即需要顯式填入申請記憶體的大小,如n * sizeof(int)
; -
返回值:
malloc
分配記憶體失敗時返回NULL
指針,可以通過返回值判斷是否分配成功; -
malloc
並不會初始化所申請的空間;
free
也是 stdlib.h
庫中的函數,聲明為 void __cdecl free(void *_Memory);
-
free
函數會將用戶釋放的記憶體塊連接到空閑鏈上; -
參數:指針
_Memory
應指向由malloc()
分配的記憶體塊,其他方式聲明的記憶體不能用free()
;
具體使用
動態創建一維數組
size_t element_cnt = 10;
int *arr = (int *)malloc(element_cnt * sizeof(int));
free(arr)
動態創建二維數組
size_t m = 10, n = 10;
int **arr = (int **)malloc(m * sizeof(int *));
for (int i = 0; i < m; i ++)
arr[i] = (int *)malloc(n * sizeof(int));
需要註意,這樣獲得的二維數組,不能保證其空間是連續的。
calloc
calloc
定義在 stdlib.h
庫中,聲明為 void *__cdecl calloc(size_t _NumOfElements,size_t _SizeOfElements);
calloc
與 malloc
的主要區別有:
-
原理:
calloc
函數會將申請的空間逐一初始化為0
;由於自動初始化的原因,
calloc
的運行效率要低於malloc
; -
參數:
calloc
多了一個參數NumOfElements
,無需人為計算空間大小;
realloc
realloc
定義在 stdlib.h
庫中,聲明為 void *__cdecl realloc(void *_Memory,size_t _NewSize);
,用於對動態內容進行擴容
-
參數:
_Memory
為 指向原來空間的指針;_NewSize
為 擴容後空間大小 -
返回值:
如果
_Memory
後有足夠的連續空間,則擴大_Memory
指向的地址,並返回_Memory
;如果空間不夠,則按照
_NewSize
分配空間,拷貝原有數據到新分配的記憶體空間,並釋放_Memory
所指的記憶體區(自動釋放,不需要free
),同時返回新分配的記憶體區域的首地址; -
分配失敗時返回空指針
NULL
; -
若
_NewSize
小於原大小,原數據末尾可能會丟失;
new / delete
工作原理
new
和 delete
是 C++ 中的關鍵字,若要使用,需要編譯器支持。
-
返回值:
記憶體分配成功時,
new
返回對象類型的指針,類型嚴格與對象匹配;new
記憶體分配失敗時,會拋出bac_alloc
異常,如果不捕捉異常,那麼程式就會異常退出; -
new
無需顯式填入申請的記憶體大小,new
會根據new
的類型分配記憶體; -
new
分配的記憶體空間在自由存儲區; -
new
和delete
支持重載; -
不能對一塊記憶體釋放兩次或以上;
對空指針
nullptr
使用delete
操作是合法的;
具體應用
動態實例化
// 動態創建變數
int *p = new int(1234);
/* ... */
delete p;
new
動態創建對象時會經歷三個步驟:
- 調用
operator new
函數分配一塊足夠的記憶體空間(通常底層預設使用malloc
實現)以存儲特定類型的對象; - 編譯器運行相應的構造函數以構造函數,併為其傳入初值;
- 返回一個指向該對象的指針;
delete
釋放對象記憶體時會經歷兩個步驟:
- 調用對象的析構函數;
- 編譯器調用
operator delete
函數釋放記憶體空間(通常底層預設使用free
實現);
// 開闢新的對象
class A {
int a;
public:
A(int a_) : a(a_) {}
};
int main() {
A *p = new A(1234);
/* ... */
delete p;
}
{}
運算符可以用來初始化沒有構造函數的結構。初次以外,使用 {}
運算符可以使得變數的初始化形式變得同意。
struct ThreeInt {
int a;
int b;
int c;
};
int main() {
ThreeInt* p = new ThreeInt{1, 2, 3};
/* ... */
delete p;
}
動態創建數組
創建和釋放數組需要使用 new[]
和 delete[]
,new[]
運算符會返回數組的首地址。
size_t element_cnt = 5;
int *p = new int[element_cnt];
/* ... */
delete[] p;
動態創建二維數組
動態創建二維數組有以下三種方式:
-
聲明一個長度為
N * M
的一維數組,通過下標r * M + c
訪問二維數組中小標為(r, c)
的元素:int *arr = new int[N * M];
這種方法可以保證二維數組的物理空間是 連續的。
-
通過變數存儲 數組的數組 的首地址——指向一個一維數組的指針的地址。這個變數即 二重指針:
int **arr = new int*[M]; for (int i = 0; i < M; i ++) a[i] = new int[N];
需要註意,這樣獲得的二維數組,不能保證其空間是連續的。
對於這樣獲得的記憶體的釋放,需要進行一個逆向操作:想釋放每一個一維數組,再釋放存儲一維數組首地址的地址:
for (int i = 0; i < M; i ++) delete[] arr[i]; delete[] arr;
-
第三種方法用到 指向數組的指針:
int (*arr)[N] = new int[M][N]; /* ... */ delete[] arr;
這種方式得到的也是連續的記憶體,但與第一種方式相比,可以直接使用
arr[n]
的形式得到數組第n + 1
行的首地址,使用arr[r][c]
的形式訪問到下標為(r, c)
的元素。由於指向數組的指針也是一種確定的數據類型,因此除數組的第一維外,其他維度的長度均須為一個能在編譯器確定的常量。
malloc 和 new 的主要區別
特征 | new / delete | malloc / free |
---|---|---|
分配記憶體的位置 | 自由存儲區 | 堆 |
記憶體分配失敗 | 拋出異常 bac_alloc |
返回 NULL |
分配記憶體大小 | 編譯器根據類型計算得出 | 顯式指定位元組數 |
處理數組 | new[] |
人為計算數組大小後進行記憶體分配 |
已分配記憶體的擴張 | 不支持 | realloc |
分配時記憶體不足 | 可以指定處理函數或重新指定分配器 | 無法通過用戶代碼處理 |
是否可以重載 | 可以 | 不可以 |
構造和析構函數 | 調用 | 不調用 |