文章目錄
- 前言
- 思維導圖
- C/C++程式記憶體區域分類
- 動態申請 :malloc ,calloc,realloc
- malloc
- calloc
- realloc(重新分配已申請的空間)
- 三者聯系
- 動態釋放關鍵字:free
- free
- 記憶體泄露
- 常見動態記憶體錯誤
- 忘記free(記憶體泄漏)!!!!!!!!
- 不及時檢測是否申請成功(對NULL的解參考)
- 越界訪問開辟的空間
- 對非動態開辟空間的指標free
- 未完全釋放動態申請的空間
- free后忘記將野指標置為NULL
- 重復free同一塊動態記憶體
- 經典面試題
- 面試題一
- 面試題二
- 面試題三
- 面試題四
- 柔性陣列
- 序
- 柔性陣列的特點
- 柔性陣列的使用
- 柔性陣列的優勢
- 總結
前言
博主實力有限,博文有什么錯誤,請你斧正,
本文討論動態記憶體開辟的事情與注意點
本文需要函式堆疊幀的知識,見我另外一篇博客
思維導圖

C/C++程式記憶體區域分類
- 記憶體中有這幾個區:堆疊區,堆區,代碼段(也稱謂常量區),資料段(也稱謂靜態區)
- 堆疊區(stack):在執行函式時,函式內區域變數的存盤單元都可以在堆疊上創建,函式執行結束時這些存盤單元自動被釋放,堆疊記憶體分配運算內置于處理器的指令集中,效率很高,但是分配的記憶體容量有限, 堆疊區主要存放運行函式而分配的區域變數、函式引數、回傳資料、回傳地址等,
- 堆區(heap):一般由程式員分配釋放, 若程式員不釋放,程式結束時可能由OS回收 ,分配方式類似于鏈表,
- 資料段(靜態區)(static)存放全域變數、靜態資料,程式結束后由系統釋放,
- 代碼段:存放函式體(類成員函式和全域函式)的二進制代碼,
- 動態記憶體開辟申請的空間是 在堆區上,堆區上,堆區上,!!!!
- 一旦申請空間 ,無論何時都要立刻檢測是否申請成功,
動態申請 :malloc ,calloc,realloc
malloc
函式體形式
void * malloc(size_t size);
size :申請的空間大小,單位位元組
注意點
- 回傳值的型別是 void* ,malloc函式并不知道開辟空間的型別,因此需要強制轉換
- 如果申請的空間滿足要求,回傳申請空間的起始地址,反之 回傳NULL
- 如果引數 size 為0,malloc的行為是標準是未定義的,取決于編譯器,
- size_t 說明 一次性開辟記憶體是有上限的,0~4,294,967,295(42億)
EXP
int * p=(int * )malloc( 100*sizeof(int ));
struct st t =(struct st * )malloc(100 sizeof(struct st));
calloc
函式形式
? void*calloc(size_t num,size_t size);
num:申請陣列元素個數,
size : 陣列元素的大小,單位位元組
注意點
- 回傳值的型別是 void* ,calloc函式并不知道開辟空間的型別,因此需要強制轉換
- 如果申請的空間滿足要求,回傳申請空間的起始地址,反之 回傳NULL
- calloc申請的空間會初始化0;
- 如果引數 size 為0,malloc的行為是標準是未定義的,取決于編譯器,
- size_t 說明 一次性開辟記憶體是有上限的,0~4,294,967,295(42億)
EXP
int * p =(int *)calloc(100,sizeof(int ));
struct st *T =(struct st *) calloc(100,sizeof(struct st ));
realloc(重新分配已申請的空間)
函式形式
void * realloc(void*memblock,size_t size);
Memblock引數指向
動態記憶體塊的開頭,如果memblock為NULL,realloc的行為與malloc
相同,并分配一個新的大小為位元組的塊,回傳起始地址,如果memblock不為空,則它應該是上一次呼叫calloc、malloc或realloc回傳的指標,
Size引數提供塊的新大小(以位元組為單位),
如果size 大小為零且Buffer引數不為空,則回傳值為NULL 且
原始塊被釋放,如果size 不為0且memblock引數不為NULL,
若在memblock后面
不存在連續的 size 個空間,編譯器會在堆中找尋新的合適大小的空間,將原始塊內容拷貝到新的位置,并free原始塊,如果后面存在就回傳原始塊地址,若沒有足夠的空間區分配,那么回傳NULL,且原始塊
保持不變,
?
注意點
- 如果membloc 為NULL,realloc行為與 malloc相同
- realloc再找尋新的地址時,成功就會free原始塊
三者聯系
- malloc 與calloc 行為基本一樣,只是 calloc會初始化 開辟的記憶體為0 .另外calloc開辟的形式是陣列,陣列中的元素型別要一致,
- realloc 傳入的指標 是動態記憶體的地址,
- realloc 內部有free功能
動態釋放關鍵字:free
free
函式形式
void* free(void* memblock);
注意點
free釋放的必須是
動態記憶體的地址free只是釋放了堆區空間,不會改變membloc,
因此memblock記住了堆區空間的地址,但是地址的內容是隨機的,這就意味著free后 memblock 成為了危險的 ,危險的,危險的野指標!!!!!!,
對于這種情況一般我們free后將memblock置為NULL
- 向堆區動態申請的空間,要時刻及時free,不然程式過大,會出現嚴重的問題:記憶體泄漏
記憶體泄露
指程式中已動態分配的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式運行速度減慢甚至系統崩潰等嚴重后果,
常見動態記憶體錯誤
忘記free(記憶體泄漏)!!!!!!!!
int main() { int* p = (int*)malloc(100); printf("hello\n"); //忘記free,導致堆區一直被占用,如果遇到到處malloc,那么堆區的記憶體會被耗干,導致記憶體泄漏 }
不及時檢測是否申請成功(對NULL的解參考)
error
int main() { int* p = (int*)malloc(100 * sizeof(int)); *p = 100; //如果malloc回傳NULL呢?程式會crash }cor
int main() { int* p = (int*)malloc(100 * sizeof(int)); if (NULL == p) { printf("%s\n", strerror(errno));//列印錯誤資訊 assert(NULL);//通過assert,終止程式, } else { *p = 100; } }
越界訪問開辟的空間
error
int main() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE);//EXIT_FAILURE ==1,exit(1).結束程式 } for (i = 0; i <= 10; i++) { *(p + i) = i;//當i是10的時候越界訪問 } free(p); }cor
int main() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE);//EXIT_FAILURE ==1,exit(1).結束程式 } for (i = 0; i < 10; i++) { *(p + i) = i; } free(p); }
對非動態開辟空間的指標free
int main() { char* str = "hello"; free(str);//str是堆疊區區域變數 }
未完全釋放動態申請的空間
int main() { int* p = (int*)malloc(100); p++; free(p);//p不再指向動態記憶體的起始位置 }
free后忘記將野指標置為NULL
int main() { int* p = (int*)malloc(100); free(p);//忘記置NULL //p =NULL; }
重復free同一塊動態記憶體
int main() { int* p = (int*)malloc(100); free(p); //第一free后,p成為了野指標,對野指標的任何操作都會導致程式 crash free(p); }
經典面試題
面試題一
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }問題:請問運行Test 函式會有什么樣的結果
分析:
- str只是將值NULL,傳給形參變數p,
因此在GetMemory 函式堆疊幀后,p雖然被回收了,
但是申請的空間忘記free了,會導致
記憶體泄漏!!!
GetMemory后 str 仍為NULL,因此strcpy會導致程式crash
另外 printf傳入NULL也會導致crash
面試題二
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf("%c\n",*str); printf(str); }問題:請問運行Test 函式會有什么樣的結果
分析:
- GetMemory函式雖然回傳了 字符陣列的首導致,
但是GetMemory函式堆疊幀結束后,為堆疊幀開辟的空間識訓,那部分地址的內容的不變的,因此可以* str,這是雖然 printf堆疊幀,但是我已經把一個字符傳進去了,因此可以列印,
但是當 傳入一個 str時,printf堆疊幀后,訪問地址內容是隨機的,
因此我們說 str這個是非常危險,非常危險,非常危險的野指標,
面試題三
void GetMemory(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }問題:請問運行Test 函式會有什么樣的結果
分析:
輸出 :hello,但是沒有free(str)
GetMemory 的引數是二級指標,因此需要傳入 一級指標的地址,對二進指標解參考一次可以找尋到實參一級指標,因此通過這種方法可以改變實參str的指向,這是通過函式改變實參的一種方法,
因此 str就會指向堆中開辟的空間,str成為了有效指標,因此strcpy,printf 都沒有問題,但是忘記free(str)了
面試題四
void Test(void) { char *str = (char *) malloc(100); strcpy(str, "hello"); free(str); if(str != NULL) { strcpy(str, "world"); printf(str); } }問題:請問運行Test 函式會有什么樣的結果
分析:
- 未檢測是否成功分配成功,free后 str成為了野指標,對它的任何操作都是非法的,不合規則的,雖然后面的strcpy和printf看似沒有問題,
柔性陣列
序
柔性陣列是C99標準增加的 一類
只能在結構體中 定義的特殊陣列,形式:
struct st_type { int i; int a[];//柔性陣列成員 }type_a; //有些編譯器 會報錯,可以改成 struct st_type { int i; int a[0];//柔性陣列成員 }type_a;
柔性陣列的特點
- 結構中的柔性陣列成員前面
必須有至少一個其他成員,- sizeof 計算結構體大小時
不包含柔性陣列,
柔性陣列的使用
- 包含柔性陣列成員的結構用動態開辟記憶體函式(malloc,calloc,realloc)進行記憶體的動態分配,并且分配的記憶體應該大于結構的大小,以適應柔性陣列的預期大小 .
EXA
struct st_type { int t; int a[];//柔性陣列成員 }type_a; int main() { struct st_type* T1 = (struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int)); if (NULL == T1) { exit(1); } struct st_type* T2 = (struct st_type*)calloc(1, sizeof(struct st_type) + 10 * sizeof(int)); if (NULL == T2) { exit(1); } struct st_type* T3 = (struct st_type*)realloc(T1, sizeof(struct st_type) + 20 * sizeof(int)); if (NULL == T3) { exit(1); } //輸出T1 T1->t = 100; printf("%d\n", T1->t); for (size_t i = 0; i < 10; i++) { *(T1->a + i) = i; printf("%d ", *(T1->a + i)); } printf("\n"); //輸出T2 T2->t = 1000; printf("%d\n", T2->t); for (size_t i = 0; i < 10; i++) { *(T2->a + i) = i; printf("%d ", *(T2->a + i)); } printf("\n"); //輸出T3 T3->t = 1000; printf("%d\n", T3->t); for (size_t i = 0; i < 20; i++) { *(T3->a + i) = i; printf("%d ", *(T3->a + i)); } printf("\n"); //在本例子,T3重新分配空間時,已free過 T1.因此不需要再出free野指標, free(T2); T2 = NULL; free(T3); T3 = NULL; }
柔性陣列的優勢
- 柔性陣列這種只能結構體動態開辟空間時,才用到的特點,類似下面這種方式
typedef struct st_type { int i; int* p_a; }type_a; int main() { type_a* p = (type_a*)malloc(sizeof(type_a)); if (NULL == p) { exit(1); } p->i = 100; printf("%d\n", p->i); p->p_a = (int*)malloc(p->i * sizeof(int)); if (NULL == (p->p_a)) { exit(1); } for (size_t i = 0; i < 100; i++) { p->p_a[i] = i; printf("%d ", p->p_a[i]); } free(p->p_a); p->p_a = NULL; free(p); p = NULL; }
- 雖然2種方式都可以完成相同功能,但是柔性陣列的方式有二個優勢:
- 方便記憶體釋放
- 如果我們的代碼是在一個給別人用的函式中,你在里面做了二次記憶體分配,并把整個結構體回傳
給用戶,用戶呼叫free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,所
以你不能指望用戶來發現這個事,所以,如果我們把結構體的記憶體以及其成員要的記憶體一次性分
配好了,并回傳給用戶一個結構體指標,用戶做一次free就可以把所有的記憶體也給釋放掉- 訪問速度快,開辟的記憶體是
連續的
- 連續的記憶體有益于提高訪問速度,也有益于減少記憶體碎片,(其實,我個人覺得也沒多高了,反
正你跑不了要用做偏移量的加法來尋址
總結
無論何時借別人的東西(動態開辟記憶體),都要記得還(free)
任何對野指標的運算都是非法的,都會導致程式crash
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/305957.html
標籤:java





