? ? ? ? 學完前面的C語言內容后,我們之前給內存開辟空間的方式是這樣的。
int val=20;
char arr[10]={0};
? ? ? ? ?我們發現這個方式有兩個弊端:空間是固定的;同時在聲明的時候必須指定數組的長度,一旦確定了大小就不能調整的。
? ? ? ? 而實際應用的過程中,我們發現定長的數組往往是不能滿足需要的。因此我們需要對內存進行動態化的處理。
目錄
malloc函數
free函數
calloc函數
realloc函數
動態內存管理的幾個常見錯誤
對空指針解引用
對動態開辟內存的越界訪問
對非動態內存使用free函數
?使用free函數釋放了一部分
同一動態內存多次釋放
動態開辟內存忘記釋放(內存泄漏)
一些經典的內存方面的例題:
1.
2.
3.
4.
柔性數組
柔性數組的特點
? ? ? ? 柔性數組的使用
? ? ? ? 柔性數組的優勢
C/C++中內存區域劃分
內存三大區域主要存儲的數據類型。
malloc函數
? ? ? ? malloc是C語言動態內存開辟的一個函數,它的語法形式是這樣的
void * malloc(size_t size)
?????????其中size是指定的大小(字節)
? ? ? ? 這個函數就是向內存申請一塊連續可用的空間,并返回這塊空間的指針
? ? ? ? 如果開辟成功則返回一個指向開辟好空間的指針則返回一個指向開辟好空間的指針。;如果開辟失敗則返回一個NULL指針,因此一定要對malloc返回值做檢查。
? ? ? ? 返回值類型為void*,所以malloc函數并不知道開辟空間的類型,具體使用的時候使用者自己決定。
? ? ? ? 如果參數size的數值為0,則malloc的行為標準是未定義,具體行為取決于編譯器。
? ? ? ? 使用該函數前需要包含頭文件<stdlib.h>
? ? ? ? 但是當我們申請空間調用后一定要銷毀內存空間,因此我們還需要free函數
free函數
? ? ? ? free函數專門用來做動態內存的釋放和回收的函數。語法結構如下:
void *free(void *ptr)
????????ptr中存放的是要釋放的空間的起始位置。
? ? ? ? 如果ptr指向的內存空間不是動態的,free行為未定義;如果ptr指向的內存是NULL指針,則函數什么都不做。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{int *p=(int*)malloc(sizeof(int)*10);if (p == NULL){perror("空間申請失敗");return 1;//異常返回,退出程序}//使用內存for (int i = 0; i < 10; i++){p[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//釋放內存free(p);//如果這里不寫free函數則程序運行時候系統自動回收這些內存。//但是可能導致內存泄漏//同時這么寫是很危險的,因為p被釋放時候就是野指針了。后續如果接著調用p,可能會導致程序崩潰//所以要在使用完內存之后立即將p置為NULLp = NULL;return 0;
}
calloc函數
? ? ? ? calloc函數也可以用來動態內存分配,語法結構如下:
void*calloc(size_t num,size_t size)
? ? ? ? 功能是給num個size元素開辟一塊空間,并將其初始化為零。
? ? ? ? 看著與malloc的功能相似,區別是calloc在返回地址之前吧申請的空間每個字節全部初始化為0。
? ? ? ? malloc效率更高一點,calloc不需要初始化。
realloc函數
? ? ? ? realloc函數讓動態內存更加靈活的調整。
? ? ? ? 如果發現申請空間過小或者過大的時候,為了合理使用內存,靈活的調整內存的大小,而realloc函數就是為了這個而生的。
? ? ? ? 它的語法結構如下:
void*realloc(void *ptr,size_t size)
? ? ? ? ptr是要調整的內存的起始位置,size是調整后新的內存大小(單位為字節)
? ? ? ? 返回值為調整后內存起始位置。
? ? ? ? 這個函數調整原有內存的大小基礎上會將原數據遷移到新空間。
?使用realloc幾種情況
1.后面有足夠大的空間,直接擴容。
2.后面空間足夠但是被占用了,所以在新空間找一塊足夠大滿足條件的內存空間,將舊空間數據拷貝到新的空間,隨后釋放掉舊空間并返回新空間的地址
動態內存管理的幾個常見錯誤
對空指針解引用
????????
#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i < 10; i++){*(p + i) = i=1; //可能產生空指針解引用操作}return 0;
}
所以要判斷malloc返回值
對動態開辟內存的越界訪問
? ? ? ? 之前我們知道數組是不能越界訪問的。動態內存也是如此,申請的時候也是有大小的,必須要在自己的范圍內使用,超出范圍就是非法訪問。
? ? ? ? 錯誤寫法
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i <= 10; i++){*(p + i) = i=1; //當i為10的時候形成越界訪問了}return 0;
}
對非動態內存使用free函數
錯誤寫法
#include<stdio.h>
#include<stdlib.h>
int main()
{int a = 10;int* p = &a;//使用*p = 100;free(p);p = NULL;return 0;
}
?使用free函數釋放了一部分
錯誤寫法:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return -1;}int i = 0;for (i = 0; i < 5; i++){*p = 5;p++;}free(p);//p指向的不再是動態開辟的空間的起始地址。p = NULL;return 0;
}
同一動態內存多次釋放
????????? ? ? ?錯誤寫法:
#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return 1;}free(p);free(p);//釋放兩次,第二次釋放會導致程序崩潰。
}
int main()
{test();return 0;
}
? ? ? 可以這樣改正
#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return 1;}free(p);p = NULL;free(p);//釋放兩次,第二次釋放會導致程序崩潰。
}
int main()
{test();return 0;
}
動態開辟內存忘記釋放(內存泄漏)
? ? ? ? 錯誤寫法:
#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}
}
int main()
{test();while (1);//無法知道前面申請10個字節的地址return 0;
}
正確寫法:要在函數之內釋放內存
? ? ? ? 或者也可以這樣
#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}return p;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//釋放pr = NULL;while (1);//無法知道前面申請10個字節的地址return 0;
}
? ? ? ? 只要保證一個原則:malloc、calloc、realloc必須要和free函數成對出現。
? ? ? ? realloc函數也能實現malloc函數的效果
????????但是即使你成對存在,也可能內存泄漏
????????如下圖所示,在test函數中,在釋放內存之前就已經返回了,所以內存沒有釋放,因此內存泄漏。
#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}int n = 20;if (n > 10){//代碼}return p;free(p);p = NULL;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//釋放pr = NULL;while (1);//無法知道前面申請10個字節的地址return 0;
}
一些經典的內存方面的例題:
1.
void GetMemory(char *p)
{p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(str);strcpy(str, "Hello World!");printf("%s\n", str);
}
運行test()函數后的結果:
運行崩潰。
解析:這里面,test函數中GetMemory函數的調用是直接將指針變量str本身傳遞過去了,是傳值調用,str的值沒有變化,仍然是NULL,所以在下一步進入strcpy函數,在strcpy函數中會對NULL進行解引用,造成了非法訪問,程序就會崩潰。
可以這么更改:(這種方法更好一點)
void GetMemory(char **p)
{*p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}
也可以這樣改 :
char* GetMemory(char **p)
{*p = (char*)malloc(100);return p;
}
void test()
{char* str = NULL;str=GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}
2.
char *GetMemory()
{char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}
?運行test函數的后果:
運行結果錯誤。
解析:p的地址可以正常傳遞給str,但是p數組是函數的局部變量,出了函數就會被回收,p數組的內收可能就被改了。這個就是返回棧空間地址的問題。
棧區上空間要么free函數回收,要么程序結束回收。
可以這樣改:
char *GetMemory()
{static char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}
3.
void GetMemory(char **p,int num)
{*p=(char*)malloc(num);
}
void test()
{char* str = NULL;GetMemory(&str,100);strcpy(str,"hello");printf(str);
}
求test函數的運行結果
?程序崩潰
解析:內存沒有釋放。
4.
void test()
{char *str=(char *)malloc(100);strcpy(str,"hello");free(str);if(str!=NULL){ strcpy(str,"world");printf(str);}
}
求運行test函數的結果:
運行錯誤。
解析:str在free函數之后沒有置為NULL。
這些題目出自于《高質量C/C++編程》
柔性數組
? ? ? ? 柔性數組在結構體中,且最后一個成員是未知大小的數組,這個數組就是柔性數組。
struct S
{int a;int S[];//未指明大小,就是柔性數組
};
? ? ? ? 有些編譯器可能不支持這種寫法,可以改成
struct S
{int a;int S[0];//未指明大小,就是柔性數組
};
柔性數組的特點
? ? ? ? 結構體中柔性數組前至少要有一個成員
? ? ? ? sizeof返回這種結構大小不包括柔性數組
? ? ? ? 包含柔性數組的結構用malloc進行動態內存分配,并且分配的內存應該大于該結構的大小以適應柔性數組預期大小。
#include<stdio.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性數組
}st;
int main()
{printf("%zd\n", sizeof(st));return 0;
}
結果為5。
? ? ? ? 柔性數組的使用
????????
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性數組
}st;
int main()
{st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間if (p == NULL){perror("malloc error");return -1;}//使用內存p->a = 10;p->c = 0;for (int i = 0; i < 5; i++){p->S[i] = i+1;}//空間不夠// 擴容st*q=(st*)realloc(p, sizeof(st) + 40 * sizeof(int));if (q != NULL){p = q;q = NULL;}//釋放內存free(q);q = NULL;return 0;
}
應用二:相當于獲得了10個整型元素空間
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int S[];//未指明大小,就是柔性數組
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間p->i = 100;for (i = 0; i < 5; i++){p->S[i] = i+1;}free(p);p = NULL;return 0;
}
? ? ? ? 柔性數組的優勢
? ? ? ? 上圖代碼也可以這樣寫:
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int *p_a;
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間p->i = 100;p->p_a = (int*)malloc(p->i*sizeof(int));for (i = 0; i < 5; i++){p->p_a[i] = i+1;}free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}
? ? ? ? 二者 均可,但是方法一有兩大好處:
1.方便內存釋放
2.有利于訪問速度
C/C++中內存區域劃分
C/C++中內存劃分的幾個區域
1.棧區(stack):在執行函數時,函數內部局部變量的儲存單元都可以在棧上創建。函數執行結束時這些儲存單元自動被釋放。棧內存分配內置于處理器指令集中,效率很高,但是分配的內存容量有限。棧區主要是存放運行函數而分配的局部變量、函數參數、返回數據、返回地址等(詳細了解可以參考《函數棧幀的創建與銷毀》)
2.堆區(heap):一般由程序員分配釋放,若程序員不釋放,程序結束可能由操作系統釋放。分配方式類似于鏈表
3.數據段(靜態區):(static)存放全局變量、靜態數據。程序結束后由系統釋放
4.代碼段:存放函數體(類成員函數和全局函數)的二進制代碼段
具體可以參考如下:
????????
感謝看到這里的讀者大大們,求一個贊,謝謝