Dynamic Memory Allocation(動態記憶體分配)
?
1.為什么存在動態記憶體分配
2.動態記憶體函式的介紹
malloc
free
calloc
realloc
?
3.常見的動態記憶體錯誤
4.幾個經典的筆試題
5.柔型陣列
?
?
?
在開始之前,我們需要回顧一下 我們當前所掌握的 記憶體使用方法
當前我們知道的記憶體使用方法
1. 創建一個 變數
int a =10;// (假設)區域變數 - 堆疊區
int g_a =10; // (假設)全域變數 - 靜態區
?
?
2. 創建 一個陣列
區域變數 - 堆疊區
全域變數 - 靜態區

?
程式一:
#include<stdio.h>
struct s // 班級成員資訊
{
char name[20];
int age;
};
int main()
{
struct s arr[50];// 50 個 struct s 型別的資料
// 但是如果班級沒50人,那么就意味著要浪費空間
// 反之班級人數超過50個,空間就不夠用
// 如果 在前面加上 int n =0; scanf("%d",&n); n 取代 50 的位數行不行呢?
// 答案不行 因為 n 是一個變數, 而陣列的元素個數應該給一個常量
return 0;
}
特點:
1. 空間開辟大小是固定的
2,陣列 在申明 的時候,必須制定陣列的長度,它所需要的的記憶體 在 編譯時 分配
?
C語言是可以創建變長陣列 - c99 中增加了(vs不支持,gcc支持 c99標準 -> gcc test.c - std = c99)
?
?
下面正式進入正文:
為什么存在動態記憶體分配?
對于空間的需求,不僅僅是上述的情況,有時候我們需要的空間大小在程式運行的時候才知道
那陣列的編譯時開辟空間的方式就不能滿足了,這時候就只能試試動態開辟了(在堆上申請空間)
?
?
?
?
動態記憶體函式的介紹
malloc 和 free
malloc :void* malloc(size_t size); // size 開辟的空間大小,單位位元組; void* 是一個指標, 指向的是開辟的空間的起始地址
free:void free(void* memblock); // memblock - 記憶體塊
?
程式二:
#include<stdio.h>
#include<stdlib.h>// malloc 所在頭檔案
#include<errno.h>// errno,h 錯誤碼庫
int main() // 與 函式 strerror(錯誤資訊報告) 搭配使用,將錯誤碼轉化為錯誤資訊,并回傳它的地址
{
// 我想向記憶體申請 10個 整形的 空間
int* p = (int*)malloc(/*INT_MAX*/10 * (sizeof(int)));// 因為 malloc 函式 回傳的是 萬能*(無型別)指標,所以需要轉化型別
// malloc 函式 也有 開辟空間失敗的時候,
// 比如 記憶體 只有 4 個 g ,我要開辟 8 g 空間,這肯定是會失敗的,回傳 空指標 NULL
if (p == NULL)
{
// 找出列印錯誤原因的方式
printf("%s\n", strerror(errno));// malloc(INT_MAX)
//輸出 Not enough space
}
else
{
// 正常使用 空間
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;// 賦值 0 1 2 3 4 5 6 7 8 9
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));// 輸出 0 1 2 3 4 5 6 7 8 9
}
printf("\n");
}
// 當動態申請的空間 不再使用的時候
// 就會把空間 還給 作業系統
// 這時候 就會用到 free 函式
return 0;
}
?
?
說到 free 函式, free 函式 是專門用來做 動態記憶體 的 釋放 和 回收 的
?
程式三:
#include<stdio.h>
#include<stdlib.h>// malloc,free 所在頭檔案
#include<errno.h>
int main()
{
// 我想向記憶體申請 10個 整形的 空間
int* p = (int*)malloc(10 * (sizeof(int)));
if (p == NULL)
{
// 找出列印錯誤原因的方式
printf("%s\n", strerror(errno));// malloc(INT_MAX)
//輸出 Not enough space
}
else
{
// 正常使用 空間
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;// 0 1 2 3 4 5 6 7 8 9
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));// 0 1 2 3 4 5 6 7 8 9
}
printf("\n");
}
// 當動態申請的空間 不再使用的時候
// 就會把空間 還給 作業系統
// 這時候 就會用到 free 函式
free(p);// 用完了再釋放,也就是說 在列印完之后(呼叫完之后),再釋放(p的值沒有改變,還可以通過它找到該空間)
// 雖然當前空間 不屬于當前程式,但是依然 可以通過 p 找到 該空間,很有可能會破壞這個空間
// 所以先在這個 指標 依然很危險
p = NULL; // free函式 釋放完空間后,不會改 p 的值,所以我們自己主動改
return 0;
}
// 最后請注意:free 是用來 釋放 動態開辟的記憶體
// free 函式 的 特殊情況:
//1. 如果引數 ptr 指向的空間不是動態開辟的,那 free 函式的行為是未定義的
//2. 如果引數 ptr 是 NULL 指標,則函式 什么 都不做,
為了方便你們進一步了解 free 函式
舉個 special 例子:
你呢,跟你 girlfriend 前期 相處非常好,你把女友電話記得牢牢的,有助于交流,
但是,有一天 你女朋友看不上你了,就 free§ [ 跟你分手了 ]
但是你(p)還死皮賴臉的記住你前女友的號碼(動態開辟空間的地址),隨時可能打電話騷擾她,并且找到她,影響她的生活,說明你很危險(訪問該空間內容,并修改,該指標很危險)
這時候,你前女友為了你能斷開念想 (其實保護自己 == 維護程式安全)
你前女友 給你當頭一棒(爆頭),讓你失憶了,(p = NULL)
我只能說 殺人誅心!!!(還好我單身,暫時不用擔心被爆頭的危險,)
?
?
?
?
calloc 函式 :void* calloc (size_t num,size_t size);
num 元素個數,size每個元素的長度【大小】(位元組)
1. 函式的功能是:開辟一塊空間 num*size ( num 個 大小為 size 的 元素) ,并且把空間的每個位元組初始化為 0
2.與函式 malloc 的區別 只在于 calloc 會在回傳 地址之前 把申請的空間的 每個位元組初始化為全0
?
程式四:
用法跟 malloc 函式差不多
#include<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{ // 開一塊空間,空間大小為 10 * int == 40 位元組
int* p = (int*)calloc(10, sizeof(int));// 比 malloc 函式 多一個元素個數 引數
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 10; i++)// 因為 p 為整形指標變數,指向一個 int 型別 元素,所以有 10個 int 元素
{
printf("%d ", *(p + i));// 0 0 0 0 0 0 0 0 0 0
} //calloc 在開辟好空間后,會把空間的所有位元組內容全部初始化為 零
}
free(p);
p = NULL;// 狗頭拿來!!
return 0;
}
?
?
realloc 函式 : void* realloc (void* memblock,size_t size);
memblock - 記憶體塊 (指標 指向 之前已經開辟了的記憶體塊)
size : 新的大小
1. realloc 函式 的 出現 讓動態記憶體管理更加靈活
2. 有時我們會發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了, 為了合理的使用記憶體,我們一定會對 記憶體的大小 做靈活的調整, realloc 函式就可以做到 對 動態開辟記憶體 大小的調整,
3. memblock 是要調整的記憶體塊的地址
4.size 調整之后 新空間的大小
5.回傳值為 調整之后的記憶體起始位置
6.這個函式調整 原記憶體空間大小 的基礎上,還會將原來記憶體中的資料移動到 新 的空間
7.realloc 在 調整記憶體空間 時,存在兩種情況:

?
程式五:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(20);// 開辟 20 byte 動態空間
if (p == NULL) // 防止malloc 開辟動態空間失敗
{
printf("%s\n", strerror(errno));// 列印錯誤資訊
}
else
{ // 正常使用空間
int i = 0;// 20 位元組 == 5* (sizeof(int))
for (i = 0; i < 5; i++)
{
*(p + i) = i;// 此時 就是在使用 malloc 函式開辟的空間(20byte)
}
}
// 假設 malloc 函式 開辟的 20 byte 的空間,不滿足我們的需求(不夠)
// 我們希望能夠有 40 個 byte 的空間
//這里 就可以使用 realloc 函式 來調整動態開辟的記憶體
int* p2 = (int*)realloc(p, 40);// 10 * int == 40 byte
/* p */
// 記住 只要開辟動態空間的時候,最好判斷一下,無論是 malloc ,calloc還是realloc 函式 都有可能開辟空間失敗
// 在下面的解決方案中,已得到解決
// 你會發現 原本 整個空間是 由 p 來維護的
// 但是 現在你會發現 變成了 p2 來維護 了
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p2/* p */ + i));// 0 1 2 3 4 隨機值1 隨機值2 隨機值3 隨機值4 隨機值5
} // 隨機值 是因為 malloc 不會像 calloc 函式 一樣開辟完動態空間之后,初始化為 0
printf("\n");
for (i = 5; i < 10; i++) // 使用 增大后的空間
{
*(p2/* p */ + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p2/* p */ + i));// 0 1 2 3 4 5 6 7 8 9
} // 如果保持統一 ,p2 換成 p,會出現問題(p已將找不到了)
// 因為 realloc 函式,是重新創建一個(40 byte)空間
// 把 p 的內容拷貝下來,把拷貝下來的資料 放進 這新創建的空間里
// 然后把 p 釋放(p = NULL)
// 最后 回傳新空間的地址
// 輸出這個新創建的空間 的結果 讓你 感覺 空間確實變大了
// 實際上 已經 不是 原先的空間 了
return 0;
}
?
解決方案:
程式六:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
int i = 0;// 20 位元組 == 5* (sizeof(int))
for (i = 0; i < 5; i++)
{
*(p + i) = i;// 此時 就是在使用 malloc 函式開辟的空間(20byte)
}
}
int* ptr = (int*)realloc(p, 40);// 拿一個新的指標變數 ptr 先來接收 realloc 的回傳地址
if (ptr != NULL) // 判斷 realloc 開辟動態空間是否 成功
{
p = ptr;// 防止開辟失敗,把 p 改掉了
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));// 0 1 2 3 4 隨機值1 隨機值2 隨機值3 隨機值4 隨機值5
}
printf("\n");
for (i = 5; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));// 0 1 2 3 4 5 6 7 8 9
}
}
// 釋放記憶體
free(p);// 如果 realloc 開辟動態空間失敗,回傳 NULL,而 free(NULL),free 函式什么都不會做
p = NULL;
// 一定 要釋放記憶體,并置為空指標,永絕后患!
return 0;
}
realloc 如果 p 指向的空間之后 有 足夠的記憶體空間可以追加,則追加上,回傳原先的舊地址(p)
如果 p 指向的空間之后 沒有 足夠的記憶體空間可以追加,則重新找一個新的記憶體區域,開辟一塊新的空間, 來滿足你對空間需求,同時把 原來記憶體 的 資料 拷貝下來,把拷貝下來的資料放到新空間里,且把原來的空間釋放,最后回傳新空間的地址
?
?
另外再補充一點
realloc 函式 可以實作與 malloc 函式 一樣的功能
程式七:
#include<stdio.h>
int main()
{
int* p = (int*)realloc(NULL, 40);// 此時它的功能 與 malloc 函式一樣
// == (int*)malloc(40);
}
?
?
?
?
常見的動態記憶體錯誤
1. 對 NULL 指標的解參考 操作
程式八:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
// 萬一 malloc 失敗,p 被賦值為 NULL
for (int i = 0; i < 10; i++)
{
*(p + i) = i;// 非法地址,對 NULL 指標 進行解參考
}
free(p);
p = NULL;
return 0;
//解決方案 :在使用開辟的動態空間之前,用 assert(p),記得加上頭檔案 assert.h; 或 加上 if(p != NULL) 判斷陳述句,對 p 進行相關的判斷
}
?
?
?
2,對動態開辟的記憶體 的 越界訪問
程式九:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(20);// 開辟了 5個 int 元素的空間
if (p == NULL)// 判斷 malloc 函式 開辟動態空間 是否成功
{
return 0; // 失敗
}
else// 成功
{
int i = 0;
for (i = 0; i < 10; i++) // 我只有 5個 int元素,而現在 要訪問 第 6~10 個 int 元素,形成了越界訪問,導致程式崩潰
{ // 改成 5 就沒問題了
*(p + i) = i;
}
}
free(p);
p = NULL;
}
?
?
?
3, 對 非動態 開辟記憶體使用 free(釋放)函式
程式十:
#include<stdio.h>
int main()
{
int a = 10;// 在 堆疊上 開辟空間
int* p = &a;
*p = 20;
free(p); // free 對 非動態 開辟記憶體 使用,會造成程式崩潰
p = NULL;
return 0;
}
?
?
?
4.使用 free 釋放動態開辟記憶體的一部分
程式十一:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 0;
}
else
{
int i = 0;
for (i = 0; i < 10; i++)
{
*p++ = i;// 解決方法 *(p+i)= i; 這樣避免了 p 的地址被改變
}
// 回收空間
free(p);// 如果這樣直接這樣釋放空間,會導致程式崩潰
// 因為 p 已經不再指向該 動態開辟空間 的 起始位置,它釋放第 10 個元素之后的空間(釋放一部分動態開辟的空間)
// free 只能釋放 動態開辟空間 的起始地址(要釋放就全釋放)
}
p = NULL;
return 0;
}
?
?
?
5. 對同一塊 動態記憶體 的多次釋放
free 釋放 一塊動態開辟空間后,那個 空間 的地址并沒有改變,此時再 free 就可能出現問題
程式十二:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p = NULL)
{
return 0;
}
// 使用
// 釋放
free(p);
free(p);// free 釋放 一塊動態開辟空間后,那個 空間 的地址并沒有改變,此時再 free 就可能出現問題
return 0;
} // 誰申請 誰回收
// 在 free(); 后面 手動 把 p = NULL; 后面就算再接上一個 free,也不會有問題
// 因為 根據C語言標準:free(NULL); free 什么都不做
?
?
?
6. 對動態開辟記憶體 忘記釋放(記憶體泄露)
這種錯誤的出現,會導致計算機記憶體被耗干,導致卡死,畢竟計算機記憶體有限
程式十三:
#include<stdio.h>
#include<windows.h>
int main()
{
while (1)
{
malloc(1);
// 通過 三點(Ctrl、Alt 點 .)一線(同時按),選擇任務管理器 -》 性能
// 通過觀察 記憶體 占用 顯示發現,記憶體正在被大量消耗,
// 原因是 程式一直在申請空間,不回收,導致大量記憶體空間被占用(這些你不用,別人也用不了,因為你沒還),這就是 記憶體泄露
}
}
?
?
?
接下來我們來看幾道面試題,來加深我對 動態記憶體分配 函式的 理解
?
題目 1
程式十四:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char* p)// p 是 str 的一份臨時拷貝,即 p= NULL;
{
// malloc 在堆上開辟了一塊空間,把該動態開辟空間的起始地址賦給 p
p = (char*)malloc(100);// 此時 p 指向 malloc 開辟的動態空間
// 動態開辟空間,呼叫完了,沒有釋放空間,會造成 記憶體泄露
// 而且 這函式呼叫完后之后,這塊函式空間會被回收
// 到時我們再也找不到 這動態開辟的空間的起始地址
// 也就是說 出了這個函式 想解決 記憶體泄露 這個問題,都解決不了(p 是該函式的形參,只在該函式內部有效)
再舉個例子,
假設有一個臥底(malloc開辟的動態空間),他的資訊(空間的地址)在警察系統中都刪掉了,只有 p 知道,
后來,有一天 p 死掉了(出了函式,銷毀了 區域變數 p).他也沒有記本本告訴別人(沒有回傳這塊空間的地址),
那么這個臥底再也證明不了自己的身份(丟失了開辟動態空間的地址),就像無間道里一樣,
沒有人可以證明 他 是一個警察了(沒有人知道 這塊的空間的地址)
}
void test(void)
{
char* str = NULL;// dtr 的 空間 放了一個 空指標
GetMemory(str);// 傳值:傳的是 指標變數 str 本身(內容),不是地址
strcpy(str, "hello world");// str 還是 空指標,因為 GetMemory(str) 沒有 回傳值,也沒有通過地址 改變str的值
// 該運算式 意思是把 "hello world" 這個字串 賦給 str 這個空指標 所指向的空間(無效的空間)
// "hello world" 是不能賦過去的,這樣會導致程式崩潰,因為訪問記憶體已經失敗了
// str并沒有 指向 有效空間里,你非要把 "hello world" ,那我們就 解參考空指標,對空指標進行相印的遍歷
// 空指標的值 == 0, 0 作為地址,找一個空間,在 strcpy 肯定是要 str++ ,往后面找空間,(0 作為地址,對指向的空間賦值,然后++,再進行賦值,以此類推)
// 這樣做 必然 會 造成 非法訪問記憶體,從而導致 程式崩潰,
// 因為程式已經崩潰了,也就更談不上后面的列印,雖然 列印的格式沒有錯誤,但已經列印不出東西了
printf(str); // 通過下面一個程式,可以說明該 列印方式 沒有問題
// == printf("hello world");
}
int main()
{
test();
return 0;
}
附加 printf 程式 : printf("%s\n", str) == printf(str)
程式十五:
#include<stdio.h>
int main()
{
char* str = "abcdef";
printf("%s\n", str);// abcdef
printf(str);// abcdef
return 0;
}
?
?
現在 我們開始 幫助 這個臥底 證明他的身份了(修改程式)
改正1
程式十六:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)// str 是一個一級指標,要二級指標來接收它地址
{
// *p == str (通過地址 找到了 str)
*p = (char*)malloc(100);// 把 malloc 開辟的動態空間的起始地址賦給 *p,也就是 賦給 str
// 也就說 p 在死之前,把 臥底的身份寫在小本本上, 送給 str 這個人,所以 str 知道臥底的真實身份
}
void test(void)
{
char* str = NULL;
//傳址
GetMemory( &str );// GetMemory - 獲取一塊記憶體塊;
//把這塊記憶體 存入 str中,方便下方字串 拷貝
strcpy(str, "hello world");// 此時 str 不再是 NULL, 因為 GetMemory 賦給 它 一個 有效的 動態空間 的地址
// strcpy 可以 吧 "hello world" 賦給 str 指向的空間(malloc 函式 開辟的動態空間)
printf(str); // hello world
// str 跟這個臥底 對了一波 暗號 ==
//但是別忘了 釋放動態開辟的空間,我 們通過的 指標的方式(*p == str), 把地址帶回來了
free(str);
str = NULL; // 并 向臥底保證,目前并不會揭穿他的身份,因為臥底不想暴露身份(其實我懷疑他上癮了,)
}
int main()
{
test();
return 0;
}
?
?
改正2
程式十七:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(char* p)// str存的值是一個地址,要一個指標來接收它地址
{
p = (char*)malloc(100); //可以這么理解, 臥底(malloc 開辟的動態空間),通過 p 死之前,留下的資訊,去找 str 這個知情人,
return p;
}
void test(void)
{
char* str = NULL;
str = GetMemory(str);//傳值
// 臥底找到了 str 這個知情人
strcpy(str, "hello world");// 保險起見,對了波暗號
printf(str); // hello world
free(str); // str 確認了其身份,并保證不泄露他的身份,成為他證人
str = NULL;// 不曾想,臥底趁str不注意,宰了 str 這個人,(看來他是真的上癮了)
}
int main()
{
test();
return 0;
}
?
?
?
題目 2
程式十八:
#include<stdio.h>
char* GetMemory(void)
{
char p[] = "hello world";// 區域陣列( 堆疊上 開辟 !!!!),出了函式 就會被銷毀(還給作業系統)
return p;// 這里回傳的是一個野指標
}
void test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);// 訪問野指標,屬于非法訪問,而且再次訪問 不一定就是 hello world,很可能是亂碼
}
int main()
{
test();
return 0;
}
?
?
?
題目 3
程式十九:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* *p, int num)
{
*p = (char*)malloc(num); // 通過地址 改變了 str 的值
}
void test(void)
{
char* str = NULL;
GetMemory(&str, 100);
// if(str != NULL) 或者 assert(str),判斷 malloc 開辟動態空間 是否成功,
// 萬一開閉失敗,str 為 NULL, 導致拷貝失敗,程式崩潰,這也是一個隱藏問題
strcpy(str, "hello world");
printf(str);// 列印 hello world ,但存在記憶體泄露,未釋放動態開辟的空間
// free(str);
// str = NULL;
}
int main()
{
test();
return 0;
}
?
?
?
題目 4
程式二十:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test(void)
{
char* str = (char*)malloc(100);
if (str)// 判斷 malloc 函式 是否開辟空間成功
{
strcpy(str, "hello");
free(str);// 釋放了記憶體,但沒有 將其 置為 NULL
//加上 str = NULL; 沒有問題
if (str != NULL)// 條件為真
{
strcpy(str, "world");// 非法訪問 記憶體,str 所指向的空間已不屬于它
printf(str);// 可能輸出 world ,也可以是亂碼,因為這塊空間已經被釋放,你再對它進行訪問操作
// 什么情況都可能出現
}
}
}
int main()
{
test();
return 0;
}
?
?
?
?
柔型陣列
在 c99 標準中,結構中的最后一個元素允許是未知大小的陣列,這叫做 柔型陣列 成員
?
?
柔性陣列的 特點:
1.結構體中的柔型陣列成員前面必須至少一個其他成員
2.sizeof 回傳的這種結構大小不包括柔型陣列的記憶體
3.包含柔型陣列成員的結構 用 malloc 函式 進行記憶體的動態分配,并且分配的記憶體 應該大于 結構的大小,以適應 柔型陣列 的 預期大小
?
?
?
程式二十一:
#include<stdio.h>
struct s
{
int n;
// 第一種寫法
int arr[];// 最后一個成員,可以是未知大小的
// 第二種寫法
// int arr2[0];
// 以上 這種陣列 的 大小是未知的 ,被稱為 柔性陣列 成員
// 柔性 : 這個 陣列 的 大小 是 可調整的
};
int main()
{
return 0;
}
因為編譯器的原因,可能有一種寫法不支持,這時可以寫另一種寫法
?
?
?
?
柔性陣列的使用
程式 二十二:

#include<stdio.h>
#include<stdlib.h>
struct s // 該結構體大小為 4
{
int n;
int arr[];
};
int main()
{
//struct s s; //sizeof 回傳的這種結構大小不包括柔型陣列的記憶體
//printf("%d\n", sizeof(s));// 4, 也就說沒有包含 這個 柔性陣列 的大小
struct s* ps = (struct s*)malloc(sizeof(struct s) + 5 * sizeof(int));
// 開辟 24 byte 的空間
// 口口口口 口口口口口口口口口口口口口口口口口口口口
// n arr
// 然后把這塊空間的地址賦給 結構體指標變數 ps
// 對于 ps 來說 這塊空間是 一個結構體的空間,前四個位元組 是 成員 n 的空間, 后 20 個位元組 是 成員 arr 的空間
int i = 0;
if (ps)// 判斷 malloc 開辟動態空間是否成功 如果 ps = NULL == 0,因為 NULL 的值 為 0,條件為假跳過if陳述句
{
ps->n = 100;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;// 0 1 2 3 4
printf("%d ", ps->arr[i]); // 0 1 2 3 4
}
printf("\n");
// 假設 前面 的 5 個int, 不夠怎么辦,要10個int , 這時就要用 realloc 函式 來擴展
struct s* ptr = (struct s *)realloc(ps, 44);// 4 byte 結構體大小 + 柔性陣列的大小 40 byte
if (ptr)// 判斷 realloc 創建 動態 是否成功
{
ps = ptr;
for (i = 5; i < 10; i++)
{
ps->arr[i] = i;// 5 6 7 8 9
}
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]); // 0 1 2 3 4 5 6 7 8 9
}
free(ps);// 用完 動態空間,一定要記得釋放
ps = NULL;
}
}
return 0;
}
?
?
?
程式 二十三 (替代柔型陣列的方法):


#include<stdio.h>
#include<stdlib.h>
struct s
{
int n;
int* arr;// arr 整形指標
};
int main()
{
struct s* ps = (struct s*) malloc(sizeof(struct s));// n 和 指標變數 arr 的 空間
int i = 0;
if (ps)
{
ps->arr = malloc(20);// 開辟一塊 20 byte 的動態空間,將其地址 賦給 指標變數 arr
if (ps->arr)
{
for (i = 0; i < 5; i++)// 賦值
{
ps->arr[i] = i;
printf("%d ", ps->arr[i]);// 0 1 2 3 4
} // 圖方便 少寫一個 列印 for 回圈
printf("\n");
}
//調整大小
int* ptr = (int*)realloc(ps->arr,40);// 開辟一塊 40 byte 的動態空間,將其地址 賦予 指標變數 arr
if (ptr) // 判斷 realloc 開辟動態空間是否成功
{
ps ->arr = ptr;
for (i = 5; i < 10; i++)
{
ps->arr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);// 0 1 2 3 4 5 6 7 8 9
}
}
}
free(ps->arr);// 要先釋放arr,如果先釋放 ps 會找不到指標變數 arr
free(ps);
ps->arr = NULL;
ps = NULL;
return 0;
}
?
?
?
那么 柔型陣列 和 替代方法 有何區別
?
?
我們先來對比一下:
柔性陣列方法里(在 c99 標準中),在計算 結構體大小時,是沒有包含 arr 陣列的大小,而且開辟空間之前,我們主動算一個大小,把 n 和 陣列arr 的大小(空間),一次性開辟成功,再交由 ps(接收開辟動態空間的地址) 來維護,柔性陣列 其實是 把 n 和 陣列arr 開辟到一塊連續的空間里)
?
而 第二種方法: 先 malloc 創建這個結構體 空間(包含 n 和 arr),然后 再由 malloc 開辟出一個動態空間,將其地址嫁給 指標變數 arr,( malloc 開辟了二次動態空間)
?
區別:
柔性一次性開辟好空間, 替換的方法 需要 二次
意味著釋放(free)動態空間的時候,柔性 一次釋放,替換的方式需要 二次
那么就是說?在替換方法中?malloc 開辟的動態空間越多, free也就越多,就很有可能會出現?忘記釋放動態空間?而造成?記憶體泄漏?的問題,或者說 釋放 錯誤物件 的 空間
而 在柔性陣列中,只需要 malloc 一次,free也只需要一次,這樣出錯概率小
?
?
?
注意 malloc 函式 在開辟空間的時候,它覺得那個空間是空的,就在那里開辟空間
這樣開辟開辟的方式,必然會導致 記憶體 里面 會有一些 空間 不大不小,導致無法使用,而造成空間的浪費(記憶體碎片)
記憶體碎片 越多,空間利用率 越差,(malloc 用的越多,出現記憶體碎片概率就越高,空間的利用率越低)
所以 使用 柔性陣列,出現的記憶體碎片的概率很低,空間利用率高,

替換柔性陣列的方法: 根據 malloc 函式的 特性:它覺得那個空間是空的,就在那里開辟空間
它所創建 空間 不是連續的(就是 a 這里 我創建一塊,b哪里創建一塊空間,在記憶體上 兩者之間并不相連[連續],)
柔性陣列 : n 和 arr 都在一塊連續的記憶體塊 上存盤 (在一塊連續的記憶體塊上,訪問記憶體 的 效率更高)
?
?
?
?
在這里我們先來了解一些東西
存盤器 - 金字塔層次結構:越靠近CPU速度越快,容量越小,價格越貴
金字塔層次結構

當 CPU 處理資料的時候 ,從暫存器里拿,因為它更快,
這時候我們就會把 記憶體的資料 , 放到 高速快取 里,高速快取的資料 放到 暫存器 里 ,CPU 在往 暫存器里拿,資料最終都是來自于記憶體
?
區域性原理:
時間區域性(temporal locality):如果一個資料被訪問了,那么它在短時間內還會被再次訪問,如 LRU 快取機制,將頻繁訪問的資料保存在記憶體中,
空間區域性(spatial locality):如果一個資料被訪問了,那么和它相鄰的資料也很快會被訪問,如果陣列的 CPU 預讀功能,
?
?
在這里我們只需了解一下,不必太過深入
當我們訪問一個記憶體(空間)資料的時候,接下來 80% 的可能性,你訪問是它周邊的 資料,
記憶體中,如果我們存放資料的時候,是連續存放的話,那么接下來,我們就把這些連續的資料 放到 暫存器 里,這樣我們的命中率就提升了,這時候計算機的訪問效率更高些,

也就是說 替換柔型陣列的方法 ,開辟 的 兩個 空間 并不連續,所在訪問是可能 第一次訪問不到,因為 太分散了(不連續)
意味著 cpu 往暫存器里拿,很有很有可能拿不到我們想要的,它就往 高速快取 里面拿,高速快取沒有,就往記憶體里拿.
這樣我們命中率大大下降,計算機的訪問的效率更低,
?
?
柔型陣列的優點:
1.開辟一塊連續的空間,命中率就提升,計算機的訪問效率更高,
2.出現 記憶體碎片 的 概率很低,空間利用率高,
3.只需要 malloc 一次,free也只需要一次,這樣出錯概率小(忘記釋放動態記憶體,或者釋放錯誤的空間物件)
所以綜合來說 來說:
柔型陣列的優點就是:方便記憶體釋放,訪問速度更快,
?
?
以上就是 動態記憶體分配(Dynamic Memory allocation)的全部內容,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/299379.html
標籤:其他
