動態記憶體管理
文章目錄
- 動態記憶體管理
- 為什么要存在動態記憶體分配
- 動態記憶體函式的介紹
- malloc
- free
- calloc
- realloc
- 注意事項:
- 動態記憶體常見的錯誤
- 對申請的動態空間不進行NULL檢查
- 對動態開辟的空間越界訪問
- 對非動態記憶體使用free釋放
- 使用free釋放一塊動態開辟記憶體的一部分
- 對同一塊記憶體多次釋放
- 動態開辟記憶體忘記釋放(記憶體泄露)
- 幾個經典的筆試題
- 1
- 2
- 3
- c/c++記憶體開辟
- 柔性陣列
- 柔性陣列的定義
- 柔性陣列的特點
為什么要存在動態記憶體分配
我們現在知道的開辟的空間的方式只有直接宣告變數,例如:
int a = 3;
char b[5] = { 1,2,3 };
我在學習陣列的時候告訴我們定義陣列時,例如:int a[n],n必須時常量,而不能是變數,當時我就在想這樣的規定是多么的不人性化,因為n是常量的時候,你必須在定時陣列的時候就預先知道陣列的大小,特別是在處理一些復雜的問題的時候陣列的大小是變化的,當時我就在想為什么不能在代碼運行程序中調整陣列的大小?直到我學到了動態記憶體管理一切疑惑才解開,
上述就是開辟的空間的兩個特點:
- 空間開辟大小是固定的
- 陣列在宣告的時候,就必須指定陣列的長度,它所需要的記憶體在編譯的時候分配,
在這里多補充一下計算機記憶體的結構:

我們平時申請的變數其實都是在堆疊區上申請的,下面介紹的動態記憶體開辟函式都是在堆區上開辟的
動態記憶體函式的介紹
malloc
c語言提供了一個動態記憶體開辟的函式
void* malloc(size_t size);
這個函式向記憶體申請一塊連續可用的空間,并回傳指向這塊空間首地址的指標:
- 如果開辟成功,則回傳一個指向開辟好空間的指標
- 如果開辟失敗,則回傳一個NULL指標,因此malloc回傳值一定要做檢查,
- 回傳值的型別是void*,所以malloc函式并不知道開辟空間的型別,具體在使用的時候使用者自己來決定,
- 如果引數size為0,malloc的行為是未定義的,取決于編譯器,
free
c語言專門提供了一個回收和釋放動態記憶體的函式
void free(void* ptr);
- 如果引數ptr指向的空間不是動態開辟的,那free函式的行為是未定義的(只能釋放動態開辟的空間)
- 如果引數ptr是NULL指標,則函式什么事都不做
- 使用完free函式后,記憶體被釋放但是指標依然指向那片記憶體,所以要手動將指標置為空指標,防止指標非法訪問,
#include<stdio.h>
#include<stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = (int*)malloc(num * sizeof(int));
if (ptr == NULL) //判斷動態記憶體是否開辟成功
printf("開辟失敗");
else
{
for (int i = 0; i < num; i++)
{
ptr[i] = i;
}
}
free(ptr);//釋放開辟的動態空間
ptr = NULL;
}
這里就模擬實作了陣列的動態開辟,
calloc
c語言還提供了一個功能與malloc功能相似的函式calloc
void* calloc(size_t num, size_t size);
//引數含義是開辟num個大小為size的空間
- 函式的功能是為num個大小為size的元素開辟一塊空間,并且白空間的每個位元組初始化為0
- 與函式malloc的區別只在于calloc會在回傳地址之前把申請的空間的每個位元組初始化為全0
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("開辟記憶體失敗");
}
else
{
//使用空間
}
free(p);
p = NULL;
return 0;
}
我們開始除錯并打開記憶體監視,輸入p的地址,便可以看到記憶體將動態開辟的記憶體全部初始化為0了

realloc
realloc函式讓記憶體開辟更加方便和靈活,我有時候開辟的空間太小,有時候開辟的過大,realloc
可以對動態記憶體進行靈活的調整,函式原型如下:
void* realloc(void* ptr, size_t size);
- ptr是要調整的記憶體地址
- size是調整之后的新大小
- 回傳值為調整之后的記憶體起始位置(是整個動態記憶體的起始位置,而不是增加的記憶體起始位置)
- 這個函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到新的空間(注意新空間包括原來的空間和調整引數空間的大小,回傳的地址是新空間的首地址)

- realloc在開辟空間時存在兩種情況:
1. 原有空間后有足夠大的空間


這種情況就是直接在原有記憶體之后直接追加空間,原來空間的資料不發生改變,
2. 原有空間后沒有足夠大的空間

解決方法時在堆空間上另找一個合適大小的連續空間來使用,函式回傳一個新的記憶體地址,舊地址會被free掉
注意事項:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = (int *)realloc(NULL, 100);
if (ptr == NULL)
printf("開辟失敗");
else
{
//擴容方法一
ptr = (int *)realloc(ptr, 100);
//擴容方法二
int* p = NULL;
p = (int*)realloc(ptr, 100);
if (p != NULL)
ptr = p;
free(ptr);
ptr = NULL;
}
}
兩種擴容方法,方法一存在一個錯誤就是當擴容失敗的時候realloc會回傳空指標,空指標賦給ptr會導致ptr儲存的地址丟失,而方法二更加保險,創建了一個指標來接受庫容后的地址,然后進行判斷最后賦值,
動態記憶體常見的錯誤
對申請的動態空間不進行NULL檢查
void main()
{
int* p = (int*)malloc(100);
*p = 4;
free(p);
p = NULL;
}
malloc開辟的動態空間一定要檢查是不是為NULL指標!!!!!!!!!!!!!上面的代碼是妥妥的錯誤代碼,
對動態開辟的空間越界訪問
void main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p != NULL)
{
for (int i = 0; i < 11; i++)
{
p[i] = i;
}
}
free(p);
p = NULL;
}
這里動態開辟了10個int型別的空間,但是賦值的時候要賦11個值,導致了非法訪問,這點和陣列的越界訪問一樣,開辟多大的空間訪問多大的記憶體;
對非動態記憶體使用free釋放
不能使用free釋放非動態記憶體
使用free釋放一塊動態開辟記憶體的一部分
void main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p != NULL)
{
for (int i = 0; i < 10; i++)
{
*p++ = i;
}
}
free(p);
p = NULL;
}
這里p的地址被改變不在指向動態記憶體的首地址,導致free在釋放記憶體時出現問題
對同一塊記憶體多次釋放
void main()
{
int* p = (int*)malloc(10 * sizeof(int));
free(p);//p=NULL;
free(p);
}
這種情況再寫代碼的時候在函式里面釋放完之后,回主函式有釋放一遍導致錯誤,最好的解決方法是將釋放完的指標立刻置為空指標,
動態開辟記憶體忘記釋放(記憶體泄露)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
*p = 20;
}
void main()
{
while (1)
{
test();
}
}
指標p是一個區域變數,出函式就會被銷毀,這樣指向動態開辟的地址的指標就會丟失,導致這塊記憶體沒有人能找到,但是記憶體一直被占用,程式回圈下去直到記憶體開辟到不夠時崩潰,
這里補充一下動態記憶體的兩種回收方式:
- 主動釋放:利用free函式釋放
- 程式結束自動釋放
幾個經典的筆試題
1
void Getmemory(char* p)
{
p = (char*)malloc(100);
}
void test()
{
char* str = NULL;
Getmemory(str);
strcpy(str, "helloworld");
printf(str);
}
這個代碼實際上是錯誤的
str雖然是指標,但是str傳遞給Getmemory實際上是值傳遞,所以Getmemory里的形參是str的一份臨時拷貝,Getmemory函式結束就會被銷毀,所以str并不會受到影響,str依然是NULL,strcpy函式呼叫將常量字串賦給空指標就會出現錯誤
參考寫法:
void Getmemory(char** p)
{
*p = (char*)malloc(100);
}
void main()
{
char* str = NULL;
Getmemory(&str);
strcpy(str, "helloworld");
printf(str);
}
改成傳指標的地址,并用二級指標接收即可
2
char* Getmemory(void)
{
char p[] = "hello world";
return p;
}
void test()
{
char* str = NULL;
str = Getmemory();
printf(str);
}
這里Getmemory看似有回傳值,但是要注意的是常量字串“hello world”是在堆疊區上開辟的,在函式結束時,這塊記憶體空間就會被銷毀,回傳地址時沒有意義的
參考寫法:
char* Getmemory(void)
{
char *p = (char*)malloc(100);
strcpy(p, "kksk");
return p;
}
void main()
{
char* str = NULL;
str = Getmemory();
printf(str);
}
使用動態記憶體開辟,函式結束后記憶體就不會返還給作業系統了!
3
void main()
{
char* p = (char*)malloc(100);
strcpy(p, "kksk");
free(p);
if (p != NULL)
{
strcpy(p, "kksk");
printf(p);
}
}
free過后記憶體被釋放,但是指標依然保留那塊記憶體的地址,所以在使用指標訪問那塊記憶體就屬于非法訪問了,
c/c++記憶體開辟

柔性陣列
柔性陣列的定義
方式一
typedef struct st_type
{
int i;
int a[0];//柔性陣列成員
}type_a;
方式二:
typedef struct st_type
{
int i;
int a[];//柔性陣列成員
}type_a;
柔性陣列的特點
- 結構中的柔性陣列成員前面必須至少有一個其他成員
- sizeof回傳的這種結構大小不包括柔性陣列的記憶體
- 包含柔性陣列成員的結構用malloc()函式進行記憶體的動態分配,并且分配的記憶體應該大于結構的大小,以適應柔性陣列的預期大小
typedef struct s
{
int i;
int a[0];//柔性陣列成員
}s;
int main()
{
struct s* p = (struct s*)malloc(sizeof(struct s) + 10 * sizeof(int));//柔性陣列的開辟
if (p == NULL)
printf("開辟失敗");
else
{
for (int i = 0; i < 10; i++)
{
*(p->a + i) = i;
}
}
free(p);
p = NULL;
}
但是學到這里我們發現柔性陣列好像沒有存在的必要,如果不用柔性陣列,上述該如何實作?
struct s
{
int i;
int* arr;
};
int main()
{
struct s* p = (struct s*)malloc(sizeof(struct s));
if (p == NULL)
printf("開辟失敗");
else
{
p->i = 10;
int* pc = (int*)malloc(10 * sizeof(int));
if (pc != NULL)
{
p->arr = pc;
for (int i = 0; i < 10; i++)
{
*(p->arr + i) = i;
}
}
free(pc);
pc = NULL;
}
free(p);
p = NULL;
}
不用柔性陣列,這樣開辟也是完全可以的,但是申請空間的時候要申請兩次(一次是給結構體申請,一次是給陣列申請),釋放記憶體的時候也要釋放兩次,所以這就體現出了柔性陣列的好處,如果我們的代碼是在一個給別人用的函式中,你里面做了二次記憶體分配,并把整個結構體回傳給用戶,用戶呼叫free可以釋放結構體,但是用戶并不知道這個結構體內的成員也要free,所以用柔性陣列一次開辟,一次釋放十分方便,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293725.html
標籤:其他
上一篇:Android Studio中的 Image Asset Studio(圖示生成工具)
下一篇:計算機網路面試題(超詳細整理)
