他治愈了身邊所有人,唯獨沒有治愈他自己—超脫
csdn上的朋友你們好呀!!今天給大家分享的是動態內存管理
👀為什么存在動態內存分配
我們定義的局部變量在棧區創建
int n = 4;//在棧上開辟4個字節大小int arr[10] = { 0 };//在棧上開辟連續的40個字節大小
上述變量創建的特點
1. 空間開辟大小是固定的。
2. 數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。
但是對于空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程序運行的時候才能知道,那數組的編譯時開辟空間的方式就不能滿足了
int main()
{int n;scanf("%d",&n);int arr[n];}
上述代碼只能在C99標準編譯器上才行,vs系列編譯器均不支持,那我們怎么才能在運行的時候,實現上述變長數組的代碼呢??這時候就只能試試動態存開辟了。
👀 動態內存函數的介紹
malloc
函數功能:開辟內存塊
參數size_t:需要申請的字節數
返回值:申請失敗返回空指針,申請成功返回指向申請該空間首地址的指針
頭文件:stdlib.h
注意:返回指針的類型是void*,這時候需要你把該指針強制類型轉化為你想要的類型,這樣方便訪問,以及解引用,malloc申請來的空間是連續的,但是多次malloc來的是不連續的
malloc的使用
int main()
{int*p=(int*) malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){*(p + i) = i;//對每一個四個字節大小的元素賦值,這里*(p+i)的本質就是p[i];printf("%d", *(p + i));//打印每個元素}return 0;//程序正常退出}
free
功能:釋放內存塊
參數:指針接收要釋放內存塊的首地址
頭文件:stdlib.h
返回值:無
了解了這些之后,我們試一下釋放剛才malloc來的內存塊
int main()
{int i = 0;int*p=(int*) malloc(40);if (p == NULL){perror("malloc:");return 1;}for (int i = 0; i < 10; i++){*(p + i) = i;printf("%d", *(p + i));}free(p);//指針接收要釋放內存塊的首地址p = NULL;//很有必要否則p為野指針return 0;}
當p所指向的申請的空間釋放時,p指針指向隨機位置,p變成野指針。
如果我們不釋放動態內存申請的內存的時候,程序結束,動態申請內存由操作系統自動回收,如果不用free函數釋放申請好的空間,就會在程序運行結束前一直存在于堆中,造成內存泄漏
int main()
{while (1){malloc(1000);}return 0;}
我是不知天高地厚的年輕人哈哈哈哈哈
calloc
功能:申請一個數組在內存中,并且初始化為0;
參數:size_t num申請數組元素的個數,size_t size每個元素的字節大小
返回值:申請失敗返回空指針,申請成功返回指向申請該空間首地址的指針
頭文件:stdlib.h
calloc函數使用
int main()
{int i = 0;int*p=(int*) calloc(10,sizeof(int));//申請10個元素,每個元素字節大小4if (p == NULL)//如果返回空指針的話,申請失敗{perror("calloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印初始化的值}free(p);p = NULL;return 0;}
malloc和calloc的區別:與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為全0
我們可以看看malloc有沒有先初始化
int main()
{int* p = (int*)malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印每個元素}free(p);//指針接收要釋放內存塊的首地址p = NULL;//很有必要否則p為野指針return 0;//程序正常退出}
可以看到是未初始化的,放的隨機值
realloc
功能:內存塊的擴容
參數:第一個參數接收要擴容內存塊的首地址,擴容后總字節大小(包括原來的字節大小)
頭文件:stdlib.h
返回值:
realloc函數使用
int main()
{int* p = (int*)malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++)//循環打印擴容前的元素{*(p + i) = i;printf("%d ", *(p + i));}int* ptr = (int*)realloc(p, 80);//原空間夠用ptr==p,不夠用的話ptr存放新地址if (ptr != NULL)//擴容成功{p = ptr;//原空間夠用ptr==p,不夠用的話ptr存放新地址,重新將新地址給p}for (int i = 10; i < 20; i++)//擴容后新空間的{*(p + i) = i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
編譯運行
👀常見的動態內存錯誤
1.對NULL指針的解引用操作
int main()
{int* p = (int*)malloc(1000);int i = 0;//if (p ==NULL)//{// return 1;//}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
當malloc申請內存失敗,p=NULL,i=0,相當于給空指針解引用
解決辦法:對malloc函數返回值做出判斷
int main()
{int* p = (int*)malloc(1000);int i = 0;if (p ==NULL){return 1;}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
2. 對動態開辟空間越界訪問
int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <=25; i++)//越界訪問{*(p + i) = i;}free(p);p = NULL;return 0;
}
編譯運行
解決方法:人為檢查是否越界
修改:
int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <25; i++)//=25變成<25{*(p + i) = i;}free(p);p = NULL;return 0;
}
3.對非動態開辟內存進行free
int main()
{int a = 10;int* p = &a;free(p);p = NULL;return 0;
}
編譯運行
解決方案:你別手賤(🙂)
4.使用free釋放一塊動態開辟內存的一部分
int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}free(p);p = NULL;return 0;}
編譯運行
解決方案:別改變p指向的地址,或者用一個指針記錄申請內存的首地址
plan1:
int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*(p+i)= i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;}
plan2:
int main()
{int* p = (int*)malloc(100);int* q = p;if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p= i;printf("%d ", *p);p++;}free(q);q = NULL;return 0;}
5.多次free已經釋放的內存
int main()
{int* p = malloc(40);if (p == NULL){return 1;}free(p);free(p);p = NULL;return 0;
}
編譯運行
解決方案:別多次free已經釋放的內存(滑稽)
6.動態開辟內存忘記釋放
見上面
👀幾個經典的筆試題
1
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
分析:定義一個char*str,讓他指向空,str接收GetMemory()函數返回來的地址,進入GetMemory()函數,return p,只能把p[]的首地址傳回去,而p[]是局部變量,出GetMemory(),p[]銷毀,當你傳回去的時候,str接收的是野地址,str為野指針。打印不出來hello world
編譯運行:
2.
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
分析:
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);
}
分析:
但是沒有free釋放內存
運行編譯
4.
void main(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
分析:
運行編譯
👀 柔性數組
也許你從來沒有聽說過柔性數組(flexible array)這個概念,但是它確實是存在的。 C99 中,結構中的最
后一個元素允許是未知大小的數組,這就叫做『柔性數組』成員
柔性數組的特點
結構中的柔性數組成員前面必須至少一個其他成員。sizeof 返回的這種結構大小不包括柔性數組的內存。
包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。
我們可以定義一個結構體
struct pp {int a;int b[];};
int main()
{printf("%d", sizeof(struct pp));}
編譯運行
確實沒有包括柔性數組大小
柔性數組的使用
struct pp {int a;int b[];//柔性數組成員
};
int main()
{struct pp* p = (struct pp*)malloc(sizeof(struct pp) + 10 * sizeof(int));//malloc中第一個元素大小+柔性數組字節大小p->a = 4;//賦值for (int i = 0; i < 10; i++){p->b[i] = i;//賦值}for (int i = 0; i < 10; i++){printf("%d ", p->b[i]);//打印柔性數組}printf("%d ", p->a);free(p);//free掉malloc來的空間p = NULL;//p置為空指針}
我們能不能用指針代替那個柔性數組呢,我們可以將指針指向的那個地方malloc來使用
定義一個結構體
struct pp {int a;int* p;
};
int main()
{struct pp* q = (struct pp*)malloc(sizeof(struct pp));//申請結構體大小的內存if (q == NULL)//判斷申請是否成功{return 1;//異常退出}q->p = (int*)malloc(10*sizeof(int));if (q->p == NULL)//判斷申請是否成功{return 1;//異常退出}q->a = 10;//賦值for (int i = 0; i < 10; i++){q->p[i]= i;賦值}printf("%d ", q->a);for (int i = 0; i < 10; i++){printf("%d ", q->p[i]);}free(q->p);//free掉p指針指向的另一塊申請空間的內存q->p = NULL;//指針置空,防止野指針free(q);//free掉q指向的申請的內存q = NULL;}
分析:
malloc過程
釋放過程
編譯運行
上述 代碼1 和 代碼2 可以完成同樣的功能,但是 方法1 的實現有兩個好處: 第一個好處是:方便內存釋放
如果我們的代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把整個結構體返回給用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,所以你不能指望用戶來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉。
第二個好處是:這樣有利于訪問速度.
連續的內存有益于提高訪問速度,也有益于減少內存碎片,根據局部性原理,連續存放的數據,cup從緩沖區讀取的快,緩存區從內存中讀取的快。
總結
本片分享了四個動態內存函數,以及常見動態內存錯誤,幾個經典的筆試題,以及柔性數組的概念,如果你覺得對你有幫助的話,希望能留下你的點贊,關注加收藏,如果有不對的地方,可以私信我,謝謝各位佬們!!!