【C語言】——動態內存管理
- 一、動態內存管理概述
- 1.1、動態內存的概念
- 1.2、動態內存的必要性
- 二、 m a l l o c malloc malloc 函數
- 2.1、函數介紹
- 2.2、應用舉例
- 三、 c a l l o c calloc calloc 函數
- 四、 f r e e free free 函數
- 4.1、函數介紹
- 4.2、應用舉例
- 五、 r e a l l o c realloc realloc 函數
- 5.1、函數介紹
- 5.2、應用舉例
- 六、常見的動態內存錯誤
- 6.1、對NULL指針進行解引用
- 6.2、對動態開辟空間的越界訪問
- 6.3、對非動態開辟的內存使用 f r e e free free 釋放
- 6.4、使用 f r e e free free 釋放一塊動態開辟內存的一部分
- 6.5、對同一塊動態內存多重釋放
- 6.6、動態開辟內存忘記釋放(內存泄漏)
- 七、動態內存經典筆試題分析
- 7.1、題一
- 7.2、題二
- 7.3、題三
- 7.4、題四
- 八、柔性數組
- 8.1、什么是柔性數組
- 8.2、柔性數組的特點
- 8.2、柔性數組的使用
- 8.3、柔性數組的優勢
- 九、C/C++中內存區域劃分
一、動態內存管理概述
1.1、動態內存的概念
??在了解為什么要有動態內存管理之前,我們得先知道動態內存的定義。
??動態內存是指動態的內存空間
,意思就是:能動態開辟的內存空間,動態就是申請了這塊空間后,可動態的修改這塊空間的大小,根據需要,動態地釋放和分配內存空間
。
??
1.2、動態內存的必要性
??為什么要有動態內存呢?
??既然有動態內存,那與之相對的就是靜態內存
??什么是靜態內存呢?其實靜態內存我們天天都在用,只是不知道它是靜態內存而已
??
下面兩種內存開辟方式就是靜態內存
int val = 20;//在棧空間上開辟四個字節
char arr[10] = { 0 };//在棧空間上開辟10個字節的連續空間
??
但是靜態內存的開辟有兩個缺點:
- 空間開辟的大小是固定的
- 數組在申明的時候,必須指定數組的長度,數組空間一旦確定了大小不能調整
??但是,在實際需求中,我們往往只有在程序運行時才能知道所需的空間大小,用數組開辟空間,往往容易造成內存溢出
(空間開小),或者內存浪費
(空間開大),無法滿足實際的需求
??
??因此,C語言引入了動態內存開辟
,讓程序員自己可以申請和釋放空間,并根據需要,自己調整開辟后空間的大小。這樣不僅提高了內存的利用率,也極大地增強了程序的靈活性與擴展性。
??
??
二、 m a l l o c malloc malloc 函數
2.1、函數介紹
??C語言中提供了一個動態內存分配的函數: m a l l o c malloc malloc
功能:向內存申請一塊連續可用的空間
(可當成數組),并返回指向這塊空間的指針
-
參數 s i z e size size_ t t t s i z e size size:
- 分配的
內存的大小
,以字節為單位。即開辟 s i z e size size 字節大小的空間 - 如果參數為
0
, m a l l o c malloc malloc 的行為是標準未定義
的,取決于編譯器
??
- 分配的
-
返回值 v o i d void void *:
- 返回指向開辟空間的指針,因為 m a l l o c malloc malloc 函數 事先并不知道使用者開辟空間存放什么類型的數據,因此指針為 v o i d void void* 類型,以便能接受所有類型。
- 使用者可根據自己的需要,將其強制類型轉換成自己所需要的類型,以便能進行解引用操作。
- 如果開辟失敗,則返回 空指針,因此 m a l l o c malloc malloc 的返回值一定要做檢查
??
2.2、應用舉例
#include<stdlib.h>int main()
{int* p = NULL;p = (int*)malloc(10 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}return 0;
}
上述代碼是使用 m a l l o c malloc malloc 函數開辟 10 個整型變量的空間,也即 40 個字節的空間
- 首先,因為 m a l l o c malloc malloc 函數的返回值是指針,我們需用指針變量 p p p 接收其返回值,創建 p p p 時,并不知道其指向的空間,所以先初始化為 NULL。
- 接著,使用 m a l l o c malloc malloc 函數開辟空間,因為我們要存放的是整型變量,而 m a l l o c malloc malloc 的返回值類型為 v o i d void void* 我們通過強制類型轉換將其返回類型轉換為 i n t int int* ,并用 p p p 來接收
- 因為 m a l l o c malloc malloc 函數有可能開辟失敗1,只有當
返回值不為空
的情況我們才使用它,因此需判斷 p p p 指針是否為空。若為空,則用 p e r r o r perror perror 函數2打印出錯誤信息,并返回 13。 - 若開辟成功,我們就可以愉快地使用這塊空間啦
??需要注意的是: m a l l o c malloc malloc 開辟的空間并不會將其初始化
??
??
三、 c a l l o c calloc calloc 函數
??開辟動態內存空間,C語言還提供了一個函數叫 c a l l o c calloc calloc ,原型如下:
- 函數的功能是為 n u m num num 個大小為 s i z e size size 的元素
開辟一塊空間
,并將這塊空間初始化為 0 - 與函數 m a l l o c malloc malloc 的區別只在于 c a l l o c calloc calloc 會返回地址之前把申請的空間的每個字節初始化為全 0
舉個例子:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){int i = 0;for (i = 0; i < 10; i++){printf(" %d", *(p + i));}printf("\n");}return 0;
}
??
運行結果:
??
??
四、 f r e e free free 函數
4.1、函數介紹
??
??上述 m a l l o c malloc malloc 函數、 c a l l o c calloc calloc 函數以及后面講的 r e a l l o c realloc realloc 函數所申請的空間,并不滿足作用域的規則
。只有當程序退出
時,用他們開辟的動態空間才會歸還給操作系統,換言之,程序不退出,就不會主動歸還空間
。這時,我們就需要 f r e e free free函數 來對其主動釋放
?? f r e e free free 函數是專門用于動態開辟的內存空間的釋放和回收,聲明如下:
??
f r e e free free 函數用來釋放動態開辟的內存
- 如果參數 p t r ptr ptr 指向的空間不是動態開辟的,那 f r e e free free 函數的行為是未定義的
- 如果參數是 p t r ptr ptr 是NULL指針,則函數什么事都不做
- 需要注意的是, f r e e free free 函數
不會改變指針所指向的值
,釋放后它依然指向相同的內存空間。因此我們要手動將釋放后的 p t r ptr ptr 置空,避免出現野指針。
m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free 函數的聲明都在 < s t d l i b . h > <stdlib.h> <stdlib.h> 中
??
4.2、應用舉例
??
我們來看個例子:
#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr = NULL;ptr = (int*)malloc(10 * sizeof(int));if (NULL != ptr){int i = 0;for (i = 0; i < 10; i++){*(ptr + i) = 0;}}free(ptr);ptr = NULL;return 0;
}
??
??那我們來看看下面這種情況行不行呢?
int main()
{int* ptr = NULL;ptr = (int*)malloc(5 * sizeof(int));if (NULL != ptr){int i = 0;for (i = 0; i < 5; i++){*ptr = 0;ptr++;}}free(ptr);ptr = NULL;return 0;
}
??當然是不行的,為什么呢?
??因為傳遞給 f r e e free free 函數的是要釋放空間的 起始地址
??上面函數的 p t r ptr ptr 以及不再指向要釋放空間的起始地址了,當然是不行的。
??
??
五、 r e a l l o c realloc realloc 函數
5.1、函數介紹
??
??可能有小伙伴問:前面你說動態內存可根據需要,動態調整所開辟空間的大小
,但前面介紹 m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free函數 都只是在將動態空間的開辟和釋放,如何調整空間的大小呢?別急,我們接下來要講的 r e l l o c relloc relloc函數 就是完成調整開辟空間的大小的任務的
?? r e a l l o c realloc realloc 函數的出現讓動態內存管理更加靈活
??
??有時我們會發現之前申請的空間太小了,有時我們又會覺得申請的空間太大了,那為了合理的使用內存,,我們一定會對內存的大小做出靈活的調整。那 r e a l l o c realloc realloc函數 就可以做到對動態開辟內存大小的調整
先來看看 r e l l o c relloc relloc函數 的聲明:
- p t r ptr ptr 是要調整的內存地址
- s i z e size size 是調整之后的大小(可以變大,也可變小)
- 返回值為調整之后的內存起始位置
r e a l l o c realloc realloc 調整內存大小分為三種情況:
- 原有空間之后有足夠大的空間
??如上圖: r e l l o c relloc relloc 已經開辟 20 個字節的空間,現在我想擴容到 40 字節,同時
原有空間后方空間足夠擴展新空間
??
??此時 r e a l l o c realloc realloc 函數直接在后方追加空間,原來空間的數據不發生變化
??
2. 原有空間之后沒有足夠大的空間
??還是上面那個圖,現在我想將他擴容到 400 字節,很明顯,已開辟空間
后方沒有足夠的空間
,總不能把別人踢開,自己霸占吧
??這時, r e a l l o c realloc realloc 函數就會在堆空間 另外找一個 合適大小的空間。
??
具體流程如下:
- r e a l l o c realloc realloc 函數先在堆空間上找一塊新的空間,并且滿足大小要求
- 后將舊空間的數據拷貝到新空間中
- 接著釋放舊空間
- 最后返回新空間的起始地址
??
3. 空間調整失敗
?? r e a l l o c realloc realloc 可能出現空間調整失敗的情況,此時返回的是空指針
??
??
?? r e a l l o c realloc realloc 不僅僅能將空間的變大,還能將空間變小,只需要第二個參數的值小于原空間的大小就好了,因為縮小空間比較簡單,這里就不再過多介紹,但需要注意的是,縮小空間可能會造成數據丟失
,因此需小心使用
??同時 r e a l l o c realloc realloc函數 不僅能調整空間大小,還能完成 m a l l o c malloc malloc函數 的功能:當第一個參數 p t r ptr ptr 傳遞的是 空指針 時, r e a l l o c realloc realloc 函數就不再是調整空間大小了,你都沒空間,我還怎么調。此時 r e a l l o c realloc realloc 函數會 新開辟 s i z e size size 字節大小的空間。
??
??
5.2、應用舉例
??看了上面三種情況,大家想一想,應該怎樣接收 r e a l l o c realloc realloc 調整之后的返回值呢?
??可以直接用原來的指針 p p p 接收嗎?
??顯然是不行,如果 r e a l l o c realloc realloc 調整成功,那確實沒問題,但如果失敗了呢?此時返回的是空指針。本來 p p p 還維護著原來的空間,現在直接變空指針,那原來的空間再也找不到了,這就造成了內存泄漏。
??正確的方法是創建新的指針
p t r ptr ptr 來接收,當 p t r ptr ptr 不為 NULL,再將 p t r ptr ptr 的值傳給 p p p
??
如下:
#include<stdlib.h>int main()
{int* p = (int*)malloc(5 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}//1 2 3 4 5for (int i = 0; i < 5; i++){*(p + i) = i + 1;}//希望將空間調整為40個字節int* ptr = NULL;ptr = (int*)realloc(p, 40);if (NULL != ptr){p = ptr;}else{perror("realloc fail");}//調整成功,使用40個字節;調整失敗,繼續使用20個字節/**************業務處理**************/free(p);p = NULL;return 0;
}
??
??
六、常見的動態內存錯誤
6.1、對NULL指針進行解引用
??
#include<stdlib.h>int main(
{int* p = (int*)malloc(INT_MAX);*p = 20;//如果p的值是空指針,就會有問題free(p);p = NULL;return 0;
}
??
??動態開辟的空間,應該先對返回值進行判斷
,確保空間開辟成功
??上述代碼所要開辟的空間太大,開辟失敗,返回的是空指針,而下面一句代碼對空指針進行解引用,是錯誤的
??
#include<stdlib.h>int main()
{int* p = (int*)malloc(10 * INT_MAX);if (NULL == p){perror("malloc fail");return 1;}*p = 20;free(p);p = NULL;return 0;
}
??
6.2、對動態開辟空間的越界訪問
??
#include<stdlib.h>int mian()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
??可以看到,當 i i i = 10 時,就是對 m a l l o c malloc malloc 開辟的空間越界訪問了。
??動態內存的空間與數組是非常相似的,要注意不能對其越界訪問
。
??
6.3、對非動態開辟的內存使用 f r e e free free 釋放
void test()
{int a = 10;int* p = &a;free(p);//ok?
}
??變量 a a a 并不是動態開辟的變量,用 f r e e free free 釋放是錯誤的
??
??
6.4、使用 f r e e free free 釋放一塊動態開辟內存的一部分
??這種情況即是,傳給 f r e e free free 的指針并不是動態開辟內存的起始地址,指針跑后面去了。
void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向動態內存的起始位置
}
??注意:動態內存一定是 一同申請,一同釋放。無法做到只釋放一部分空間
??
6.5、對同一塊動態內存多重釋放
void test()
{int* p = (int*)malloc(100);//···free(p);free(p);//重復釋放
}
??這種釋放有辦法可以避免:釋放完后及時把 p p p 置為空指針,這樣,即使再次釋放,傳的是空指針, f r e e free free 什么都不會做,不會造成什么影響
??
6.6、動態開辟內存忘記釋放(內存泄漏)
#include<stdlib.h>
void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}int main()
{test();while (1);return 0;
}
??上述代碼,你會發現,一旦出了函數
,就再也找不到開辟的那 100 個字節的空間
(這代碼寫的比較極端,一直死循環,程序一直不結束) 。找不到開辟的空間更別提將其釋放,空間就一直在那占著,就造成了內存泄漏。
??想一想,如果我們一直向內存申請空間,但從來不釋放。要知道,內存的總大小是有限的,這樣就會把內存耗干
,機器就掛了。
??
總結: 用 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc 申請的空間,盡量做到:
誰(可能是函數)申請的就誰釋放
,即 m a l l o c malloc malloc 和 f r e e free free 成對出現- 如果不能釋放,要
告訴使用的人記得釋放
??
??
七、動態內存經典筆試題分析
7.1、題一
void GetMemory(char* p)
{p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main()
{Test();return 0;
}
請問運行 T e s t Test Test 函數會有什么樣的結果?
運行 T e s t Test Test 函數,程序會崩潰!
??
為什么呢?我們來分析一下
??
先來看這段代碼想要做什么:
- 首先,它定義了一個 G e t M e m o r y GetMemory GetMemory函數,很明顯,這個函數是完成動態開辟空間任務的
- 接著 T e s t Test Test函數 中創建了指針 s t r str str,將變量傳給 G e t M e m o r y GetMemory GetMemory,即希望指針 s t r str str 管理動態開辟的空間
- 最后往空間中存入 " h e l l o w o r l d " "hello world" "helloworld",并打印
??
??代碼的邏輯沒問題,那就是代碼本身出問題咯
??通過調試我們發現, G e t M e m o r y GetMemory GetMemory函數 并沒有改變 s t r str str 的值,它依然是個空指針。
??為什么呢?因為 G e t M e m o r y GetMemory GetMemory是 傳值傳參,而不是傳址傳參!傳值傳參無法改變主調函數中的值,出了函數 s t r str str 依然是空指針,而后面打印 s t r str str 指向的內容,是要對其解引用的,對空指針解引用自然會出問題。
??同時,函數中 m a l l o c malloc malloc 是實打實開辟了空間的,只有程序結束才銷毀
,而函數中的變量出了函數作用域就銷毀
,這樣函數中所開辟的 100 個字節空間出了 G e t M e m o r y GetMemory GetMemory函數 后也無法找到,造成內存泄漏=
??可能有小伙伴會問: G e t M e m o r y GetMemory GetMemory函數 的參數類型就是 c h a r char char* 啊,為什么還是傳值傳參呢?這里我們要指針傳址傳參的本質:傳遞的是變量的地址
,因為主調函數中要傳的值本身就是指針 c h a r char char* 類型,要改變指針變量,就要傳遞指針變量的指針
,即二級指針。這里可不敢看到 G e t M e m o r y GetMemory GetMemory函數 參數中是 c h a r char char* 就認為他是傳址傳參
??
正確寫法應該是這樣:
void GetMemory(char** p)
{*p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;//傳str的地址GetMemory(&str);strcpy(str, "hello world");printf(str);//釋放動態空間free(str);str = NULL;
}
當然, G e t M e m o r y GetMemory GetMemory函數 我們也可以直接讓他返回 p p p,以實現目的
char* GetMemory()
{char* p = (char*)malloc(100);return p;
}void Test(void)
{char* str = GetMemory();strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
??
??
7.2、題二
char* GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}
運行結果:
??為什么會這樣呢?
??問題還是出在 G e t M e m o r y GetMemory GetMemory函數
?? G e t M e m o r y GetMemory GetMemory函數 中創建的 p p p 數組,在出了函數作用域后就銷毀了,因此函數返回 p p p,用 s t r str str 接收,而實際上 s t r str str 接收的地址是指向一塊已經歸還的空間
,此時的 s t r str str 是野指針。再去訪問 s t r str str 所指向的空間是非法訪問,打印出的值是隨機值。
??
??
7.3、題三
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}
??可能有小伙伴會覺得這段代碼咋一看好像沒什么問題啊
??確實,大家有沒有發現代碼與我們第一題修改后的代碼非常像,但大家仔細想想它還缺少什么?
??這段代碼唯一的問題是:沒有 f r e e free free,動態申請內存空間后他并沒有還回去。
??雖然這里沒有 f r e e free free 程序也沒有問題,因此程序結束后會自動釋放空間,但以后遇到復雜的情況就不好說了,因此我們要 養成主動釋放內存空間的習慣
??
??
7.4、題四
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}
??這題的問題相信大家都能看得出來:
??
?? T e s t Test Test函數 上來先開辟 100 字節動態空間,并創建 s t r str str 變量維護它,再向空間中放 " h e l l o " "hello" "hello"
??
??但緊接著,釋放 s t r str str,卻沒將 s t r str str 置空,此時的 s t r str str是野指針。將空間釋放,即將其還給操作系統,我們是沒有使用權限了,但是這塊空間本身還在
??
??下面的 i f if if 語句判斷為真,往 s t r str str 指向的空間放入 " w o r l d " "world" "world",此時 s t r str str 指向的空間我們已經沒有使用權限了,但依然進行修改,為非法內存訪問。
??
??
八、柔性數組
8.1、什么是柔性數組
??
??也許有些小伙伴從來沒有聽過柔性數組這個概念,但是它確實是存在的
??C99 中,結構體的最后一個元素
允許是未知大小的數組
,這就叫做:柔性數組成員。
??
typedef struct st_type
{int i;int a[0];
}type_a;
??有些編譯器會報錯無法編譯,可以改成:
typedef struct st_type
{int i;int a[];
}type_a;
??
??
8.2、柔性數組的特點
- 結構體中的柔性數組成員
前面必須至少一個其他成員
- s i z e o f sizeof sizeof 返回的這種結構體
大小不包括柔性數組的內存
- 包含柔性數組成員的結構體一般用 m a l l o c malloc malloc函數 進行內存的動態分配,并且
分配的內存應該大于結構體的大小
,以適應柔性數組的預期大小
例如:
typedef struct st_type
{int i;int a[0];
}type_a;
int main()
{printf("%d\n", sizeof(type_a));return 0;
}
運行結果:
??
??
8.2、柔性數組的使用
#include<stdio.h>
#include<stdlib.h>typedef struct st_type
{int i;int a[0];
}type_a;int main()
{int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 10 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}//業務處理p->i = 10;for (i = 0; i < 10; i++){p->a[i] = i;}//調整空間type_a* ptr = (type_a*)realloc(p, sizeof(type_a) + 20 * sizeof(int));if (ptr != NULL){p = ptr;}free(p);p = NULL;return 0;
}
柔性數組的結構:
??既然這塊空間是 m a l l o c malloc malloc 出來的,也就是說他可以通過 r e a l l o c realloc realloc 來調整大小,所以這個數組可變長變短,不就是柔性嗎
??
??
8.3、柔性數組的優勢
??
??上述 t y p e type type_ a a a 結構,也可以設計為下面的結構,也能完成同樣的效果
#include<stdio.h>
#include<stdlib.h>typedef struct st_type
{int i;int* p_a;
}st_type;
int main()
{st_type* p = (st_type*)malloc(sizeof(st_type));p->i = 100;p->p_a = (int*)malloc(p->i * sizeof(int));///業務處理for (i = 0; i < 100; i++){p->p_a[i] = i;}//釋放空間free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}
圖示:
??上述兩個方法都可以達到類似的效果
??但是使用柔性數組有兩個好處:
??
- 方便內存釋放
??如果我們的代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把結構體返回給用戶。用戶調用 f r e e free free 可以釋放結構體,但是用戶并不知道結構體內的成員也需要 f r e e free free,所以你不能指望用戶來發現這個事。
??所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次 f r e e free free 就可以把所有的內存釋放掉
?? - 有利于訪問速度
??連續的內存有益于提高訪問速度,也有益于減少內存碎片4
??
??
九、C/C++中內存區域劃分
C/C++程序內存分配的幾個區域
內核空間
:操作系統核心(內核)運行的地方,在這個區域,操作系統可以直接訪問硬件,并執行特權指令。我們用戶是無權訪問這塊空間的棧區
:在執行函數時,函數內局部變量的存儲單元都是在棧上創建,函數執行結束時這些存儲單元自動釋放。棧內存分配運算內置于處理器的指令中,效率很高,但是分配的內存容量有限。棧區只要存放運行函數而分配的局部變量、函數參數、返回數據、返回地址等。堆區
:堆區一般是用來存儲程序運行期間動態分配內存的地方。堆區的內存分配是在程序運行時動態進行的,程序員可以通過調用標準庫函數(如 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc等)來在堆區中分配內存,并在不需要時手動釋放這些內存(使用 f r e e free free函數)。使用堆區需要注意內存泄漏( m e m o r y l e a k memory leak memoryleak)的問題,即程序在不再需要某塊內存時沒有釋放它,導致程序占用的內存越來越多。數據段
:數據段也叫做靜態區
,主要用來存放全局變量、靜態數據、全局變量。程序結束后由系統釋放代碼段
:存放函數體(類成員函數和全局函數)的二進制代碼、只讀常量(字符串常量)
內存開辟失敗:動態內存開辟失敗的原因一般都是所空間太大,沒有足夠的空間 ??
p e r r o r perror perror函數:有關該函數的具體介紹請看:《【C語言】——字符串函數的使用與模擬實現(下)》 ??
返回值為 1: m a i n main main 函數程序正常退出,返回值為 0;異常退出,返回值為 1 ??
我們開辟內存時,不會緊接著上一塊內存開辟,而會留下一點空隙,開辟次數越多,留下的空隙也就也多,這些空隙稱為內存碎片。 ??