
📌 本文作者: Foxny
📃 更新記錄: 2021.8.3
? 勘誤記錄: 無
💬 參考資料: 百度百科、位元科技、www.cplusplus.com、MSDN
📜 本文宣告: 由于作者水平有限,本文有錯誤和不準確之處在所難免,本人也很想知道這些錯誤,懇望讀者批評指正!
一、動態記憶體分配
0x00 引入
📚 目前我們已經掌握了以下兩種開辟記憶體的方式:
// 在堆疊上開辟4個位元組
int val = 20;
// 在堆疊空間上開辟10個位元組的連續空間
char arr[10] = {0};
📚 上述開辟空間的方式有兩個特點:
① 空間開辟的大小是固定的,
② 陣列在宣告時必須指定陣列的長度,在編譯時會開辟并分配其所需要的記憶體空間,
0x01 定義
🔍 [百度百科] 動態分配記憶體
所謂動態記憶體分配(Dynamic Memory Allocation) 就是指在程式執行的程序中動態地分配或者回收存盤空間的分配記憶體的方法,動態記憶體分配不象陣列等靜態記憶體分配方法那樣需要預先分配存盤空間,而是由系統根據程式的需要即時分配,且分配的大小就是程式要求的大小,

0x02 存在的原因
? 為什么會存在動態記憶體開辟?
💡 有時我們需要的空間大小在程式運行的時候才能知道,這時在陣列編譯時開辟空間的方式就不能滿足了,這時我們就需要動態記憶體開辟來解決問題,
二、動態記憶體函式介紹
0x00 malloc 函式

📜 頭檔案:stdlib.h
📚 介紹:malloc 是C語言提供的一個動態記憶體開辟的函式,該函式向記憶體申請一塊連續可用的空間,并回傳指向這塊空間的指標,具體情況如下:
① 如果開辟成功,則回傳一個指向開辟好空間的指標,
② 如果開辟失敗,則回傳一個 NULL 指標,
③ 回傳值的型別為 void* ,malloc 函式并不知道開辟空間的型別,由使用者自己決定,
④ 如果 size 為 0(開辟0個位元組),malloc 的行為是標準未定義的,結果將取決于編譯器,
🔍 官方介紹:http://www.cplusplus.com/reference/cstdlib/malloc/?kw=malloc

0x01 free 函式

📜 頭檔案:stdlib.h
📚 介紹:free 函式用來釋放動態開辟的記憶體空間,具體情況如下:
① 如果引數 ptr 指向的空間不是動態開辟的,那么 free 函式的行為是未定義的,
② 如果引數 ptr 是 NULL 指標,那么 free 將不會執行任何動作,
📌 注意事項:
① 使用完之后一定要記得使用 free 函式釋放所開辟的記憶體空間,
② 使用指標指向動態開辟的記憶體,使用完并 free 之后一定要記得將其置為空指標,
🔍 官方介紹:http://www.cplusplus.com/reference/cstdlib/malloc/?kw=free

💬 代碼演示:動態記憶體開辟10個整型空間(完整步驟)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// 假設開辟10個整型空間
int arr[10]; // 在堆疊區上開辟
// 動態記憶體開辟
int* p = (int*)malloc(10*sizeof(int)); // 開辟10個大小為int的空間
// 使用這些空間的時候
if (p == NULL) {
perror("main"); // main: 錯誤資訊
return 0;
}
// 使用
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = i;
}
for (i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
// 回收空間
free(p);
p = NULL; // 需要手動置為空指標
return 0;
}
🚩 0 1 2 3 4 5 6 7 8 9

? 動態記憶體開辟失敗的情況:(perror 函式)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
...
int* p = (int*)malloc(9999999999*sizeof(int)); // 獅子大開口,拿來吧你
...
}
🚩 main: Not enough space
? 為什么 free 之后,一定要把 p 置為空指標?
🔑 決議:因為 free 之后那塊開辟的記憶體空間已經不在了,它的功能只是把開辟的空間回收掉,但是 p 仍然還指向那塊記憶體空間的起始位置,這合理嗎?這不合理,所以我們需要使用 p = NULL 把他置成空指標,為了加深印象,舉一個形象的例子:

? 為什么 malloc 前面要進行強制型別轉換呢?
int* p = (int*)malloc(10*sizeof(int));
🔑 決議:為了和 int* p 型別相呼應,所以要進行強制型別轉換,你可以試著把強轉刪掉,其實也不會有什么問題,但是因為有些編譯器要求強轉,所以最好進行一下強轉,避免不必要的麻煩,
0x02 calloc 函式

📜 頭檔案:stdlib.h
📚 介紹:calloc 函式的功能實為 num 個大小為 size 的元素開辟一塊空間,并把空間的每個位元組初始化為 0 ,回傳一個指向它的指標,
? 對比:
① malloc 只有一個引數,而 calloc 有兩個引數,分別為元素的個數和元素的大小,
② 與函式 malloc 的區別在于 calloc 會在回傳地址前把申請的空間的每個位元組初始化為 0 ,
🔍 官方介紹:http://www.cplusplus.com/reference/cstdlib/malloc/?kw=calloc

💬 驗證: calloc 會對記憶體進行初始化
#include <stdio.h>
#include <stdlib.h>
int main()
{
// malloc
int* p = (int*)malloc(40); // 開辟40個空間
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
printf("%d ", *(p + i));
free(p);
p = NULL;
return 0;
}
🚩 (運行結果是10個隨機值)
#include <stdio.h>
#include <stdlib.h>
int main()
{
// calloc
int* p = (int*)calloc(10, sizeof(int)); // 開辟10個大小為int的空間,40
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
printf("%d ", *(p + i));
free(p);
p = NULL;
return 0;
}
🚩 0 0 0 0 0 0 0 0 0 0
🔺 總結:說明 calloc 會對記憶體進行初始化,把空間的每個位元組初始化為 0 ,如果我們對于申請的記憶體空間的內容,要求其初始化,我們就可以使用 calloc 函式來輕松實作,
0x03 realloc 函式

📜 頭檔案:stdlib.h
📚 介紹:realloc 函式,讓動態記憶體管理更加靈活,用于重新調整之前呼叫 malloc 或 calloc 所分配的 ptr 所指向的記憶體塊的大小,可以對動態開辟的記憶體進行大小的調整,具體介紹如下:
① ptr 為指標要調整的記憶體地址,
② size 為調整之后的新大小,
③ 回傳值為調整之后的記憶體起始位置,請求失敗則回傳空指標,
④ realloc 函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到新的空間,
📌 realloc 函式在調整記憶體空間時存在的三種情況:
情況1:原有空間之后有足夠大的空間,
情況2:原有空間之后沒有足夠大的空間,
情況3:realloc 有可能找不到合適的空間來調整大小,

情況1:當原有空間之后沒有足夠大的空間時,直接在原有記憶體之后直接追加空間,原來空間的陣列不發生變化,
情況2:當原有空間之后沒有足夠大的空間時,會在堆空間上另找一個合適大小的連續的空間來使用,函式的回傳值將是一個新的記憶體地址,
情況3:如果找不到合適的空間,就會回傳一個空指標,
🔍 官方介紹:http://www.cplusplus.com/reference/cstdlib/malloc/?kw=realloc

💬 代碼演示:realloc 調整記憶體大小
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL) {
perror("main");
return 1;
}
// 使用
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = 5;
}
// 此時,這里需要p指向的空間更大,需要20個int的空間
// realloc 調整空間
p = (int*)realloc(p, 20*sizeof(int)); // 調整為20個int的大小的空間
// 釋放
free(p);
p = NULL;
}
? 剛才提到的第三種情況,如果 realloc 找不到合適的空間,就會回傳空指標,我們想讓它增容,他卻存在回傳空指標的危險,這怎么行?
💡 解決方案:不要拿指標直接接收 realloc,可以使用臨時指標判斷一下,
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL) {
perror("main");
return 1;
}
// 使用
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = 5;
}
// 此時,這里需要 p 指向的空間更大,需要 20 個int的空間
// realloc 調整空間
int* ptmp = (int*)realloc(p, 20*sizeof(int));
// 如果ptmp不等于空指標,再把p交付給它
if (ptmp != NULL) {
p = ptmp;
}
// 釋放
free(p);
p = NULL;
}

📚 有趣的是,其實你可以把 realloc 當 malloc 用:
// 在要調整的記憶體地址部分,傳入NULL:
int* p = (int*)realloc(NULL, 40); // 這里功能類似于malloc,就是直接在堆區開辟40個位元組
三、常見的動態記憶體錯誤
0x00 對空指標的解參考操作
? 代碼演示:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int* p = (int*)malloc(9999999999);
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = i; // 對空指標進行解參考操作,非法訪問記憶體
}
return 0;
}
💡 解決方案:對 malloc 函式的回傳值做判空處理
#include <stdlib.h>
#include <stdio.h>
int main()
{
int* p = (int*)malloc(9999999999);
// 對malloc函式的回傳值做判空處理
if (p == NULL) {
perror("main")
return 1;
}
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = i; // 對空指標進行解參考操作,非法訪問記憶體
}
return 0;
}
0x01 對動態開辟空間的越界訪問
? 代碼演示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int)); // 申請10個整型的空間
if (p == NULL) {
perror("main");
return 1;
}
int i = 0;
// 越界訪問 - 指標p只管理10個整型的空間,根本無法訪問40個
for (i = 0; i < 40; i++) {
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}

💡 提醒:為了防止越界訪問,使用空間時一定要注意開辟的空間大小,
0x02 對非動態開辟的記憶體使用free釋放
? 代碼演示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arr[10] = {0}; // 在堆疊區上開辟
int* p = arr;
// 使用 略
free(p); // 使用free釋放非動態開辟的空間
p = NULL;
return 0;
}

💡 提醒:不要對非動態開辟的記憶體使用 free,否則會出現難以意料的錯誤,
0x03 使用 free 釋放一塊動態開辟記憶體的一部分
? 代碼演示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = malloc(10*sizeof(int));
if (p == NULL) {
return 1;
}
int i = 0;
for (i = 0; i < 5; i++) {
*p++ = i; // p指向的空間被改變了
}
free(p);
p = NULL;
return 0;
}

📌 注意事項:這么寫代碼會導致 p 只釋放了后面的空間,沒人記得這塊空間的起始位置,再也沒有人找得到它了,這是很件很可怕的事情,會存在記憶體泄露的風險,
💡 提醒:釋放記憶體空間的時候一定要從頭開始釋放,

0x04 對同一塊動態記憶體多次釋放
? 代碼演示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = malloc(10*sizeof(int));
if (p == NULL) {
return 1;
}
int i = 0;
for (i = 0; i < 10; i++) {
p[i] = i;
}
// 釋放
free(p);
// 一時腦熱,再一次釋放
free(p);
return 0;
}

💡 解決方案:在第一次釋放后緊接著將 p 置為空指標
// 釋放
free(p);
p = NULL;
free(p); // 此時p為空,free什么也不做
0x05 動態開辟記憶體忘記釋放導致記憶體泄漏
? 代碼演示:
#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if (p == NULL) {
return;
}
// 使用 略
// 此時忘記釋放了
}
int main()
{
test();
free(p); // 此時釋放不了了,沒人知道這塊空間的起始位置在哪了
p = NULL;
}
動態開辟的記憶體空間有兩種回收方式: 1. 主動釋放(free) 2. 程式結束
如果這塊程式在服務器上 7x24 小時運行,如果你不主動釋放或者你找不到這塊空間了,最后就會導致記憶體泄漏問題,記憶體泄漏(Memory Leak)是指程式中已動態分配的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式運行速度減慢甚至系統崩潰等嚴重后果,
💡 提醒:malloc 這一系列函式 和 free 一定要成對使用,記得及時釋放,你自己申請的空間,用完之后不打算給別人用,就自己釋放掉即可,如果你申請的空間,想傳給別人使用,傳給別人時一定要提醒別人用完之后記得釋放,
本章完
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292496.html
標籤:其他
