C語(yǔ)言動(dòng)態(tài)內(nèi)存管理分析總結(jié)
什么是動(dòng)態(tài)內(nèi)存分配
我們都知道在C語(yǔ)言中,定義變量的時(shí)候,系統(tǒng)就會(huì)為這個(gè)變量分配內(nèi)存空間,而且這個(gè)空間是在棧上開辟的,這種方式就會(huì)有兩個(gè)特點(diǎn)。
- 開辟的空間大小是固定的
- 數(shù)組在申明的時(shí)候,必須要指定數(shù)組的長(zhǎng)度,以方便為其分配內(nèi)存大小
但是這種方法并不能滿足我們?cè)陂_發(fā)中的需要,因?yàn)橛袝r(shí)候我們需要開辟的空間大小,是在程序巡行的過程中才能知道要開辟多大的。
這時(shí)候,數(shù)組的這種開辟方式就不能滿足了,因此也就有了動(dòng)態(tài)內(nèi)存分配的需要。
而動(dòng)態(tài)內(nèi)存分配,會(huì)根據(jù)需求而分配空間,大小是可以變化的,并且是在堆區(qū)上分配空間,而堆區(qū)的內(nèi)存空間大小一半都會(huì)比棧區(qū)大。
動(dòng)態(tài)內(nèi)存函數(shù)的介紹
需要需要?jiǎng)討B(tài)內(nèi)存管理,需要了解C語(yǔ)言中一下的幾個(gè)函數(shù)。
統(tǒng)一說明:一下的函數(shù)都包含在<stdlib.h>中。
free
void free (void* ptr);
為了后面可以進(jìn)行代碼演示,這里先介紹free這個(gè)函數(shù)。
我們?cè)谑褂孟嚓P(guān)函數(shù)動(dòng)態(tài)開辟了內(nèi)存空間之后,函數(shù)會(huì)返回這片空間的的首地址給使用者,當(dāng)使用者用完空間之后,需要手動(dòng)對(duì)這片空間進(jìn)行釋放,把內(nèi)存空間還給操作系統(tǒng)。
如果參數(shù) ptr 指向的空間不是動(dòng)態(tài)開辟的,那free函數(shù)的行為是未定義的。
如果參數(shù) ptr 是NULL指針,則函數(shù)什么事都不做。
釋放的空間是ptr所指向的那塊內(nèi)存空間
上面的第一種情況,有可能編譯器會(huì)報(bào)錯(cuò)。
如果我們僅僅只是開辟空間使用,而不釋放的話,會(huì)占用內(nèi)存空間資源,造成內(nèi)存泄漏,影響性能。
因此,我們需要養(yǎng)成用完就釋放的好習(xí)慣。
malloc
void* malloc (size_t size);
這個(gè)函數(shù)向內(nèi)存申請(qǐng)一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
連續(xù)可用的特點(diǎn),就像數(shù)組一樣。
如果開辟成功,則返回一個(gè)指向開辟好空間的指針。
如果開辟失敗,則返回一個(gè)NULL指針。
返回值的類型是 void* ,因此使用者需要根據(jù)自己的需求來轉(zhuǎn)化使用。
如果參數(shù)size大小為0,malloc 的行為是標(biāo)志沒有規(guī)定的,取決于編譯器。
size的大小是按照字節(jié)為單位的。
#include <stdio.h> int main() { //代碼1 int num = 0; scanf("%d", &num); int arr[num] = { 0 }; //代碼2 int* ptr = NULL; ptr = (int*)malloc(num * sizeof(int)); if (NULL != ptr)//判斷ptr指針是否為空 { int i = 0; for (i = 0; i < num; i++) { *(ptr + i) = 0; } } free(ptr);//釋放ptr所指向的動(dòng)態(tài)內(nèi)存 ptr = NULL;//是否有必要? return 0; }
上面的代碼就是malloc函數(shù)的基本使用過程,在代碼1中,這樣的使用方法,一般來說編譯器是不支持的,而第二種方法,也就是動(dòng)態(tài)內(nèi)存開辟的方法,編譯器就支持。
在最后,記得要把ptr所指向的空間給釋放掉。
這個(gè)時(shí)候,ptr種存儲(chǔ)的仍然是那塊空間的地址,這就稱為了野指針,所以我們還要把ptr置空。
calloc
與malloc類相似的,C語(yǔ)言還提供了另外一個(gè)動(dòng)態(tài)開辟內(nèi)存的函數(shù),那就是calloc。
void* calloc (size_t num, size_t size);
需要注意的是,這個(gè)函數(shù)的參數(shù)有所不同,第一個(gè)參數(shù)num是用來確定你開辟的連續(xù)空間是用來存放多少個(gè)元素的,而第二個(gè)參數(shù)size表示的是一個(gè)元素占用多少的字節(jié)。
函數(shù)的功能是為 num 個(gè)大小為 size 的元素開辟一塊空間,并且把空間的每個(gè)字節(jié)初始化為0。
與函數(shù) malloc 的區(qū)別只在于 calloc 會(huì)在返回地址之前把申請(qǐng)的空間的每個(gè)字節(jié)初始化為全0。
所以,如果我們需要在動(dòng)態(tài)開辟內(nèi)存的時(shí)候進(jìn)行初始化,我們可以使用calloc這個(gè)函數(shù)。
我們可以通過調(diào)試的監(jiān)視窗口來查看這個(gè)函數(shù)在開辟空間的時(shí)候是否幫我們初始化。
#include <stdlib.h> int main() { int* p = (int*)calloc(10, sizeof(int)); if (NULL != p) { //使用空間 } free(p); p = NULL; return 0; }
realloc
有時(shí)候我們會(huì)發(fā)現(xiàn),哪怕是使用上面的動(dòng)態(tài)內(nèi)存管理的函數(shù),也會(huì)有不方便的時(shí)候。我們可能會(huì)因?yàn)榭臻g申請(qǐng)小了不夠用而去擴(kuò)容,但是如果我們使用上面兩個(gè)函數(shù)去開辟更大的空間的時(shí)候,之前的數(shù)據(jù)拷貝過來新開辟的更大的空間又很麻煩。
這個(gè)時(shí)候,我們就可以使用realloc了。
void* realloc (void* ptr, size_t size);
ptr 是要調(diào)整的內(nèi)存地址。
size 調(diào)整之后新大小,和malloc一樣,是以字節(jié)為單位的。
返回值為調(diào)整之后的內(nèi)存起始位置。
這個(gè)函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會(huì)將原來內(nèi)存中的數(shù)據(jù)移動(dòng)到新的空間。
在realloc函數(shù)使用的時(shí)候,會(huì)出現(xiàn)兩種情況:
原有的空間之后還有足夠的空間
如果原來的內(nèi)存空間之后還有足夠的空間來滿足新的大小,這時(shí)候就會(huì)直接對(duì)原來的空間進(jìn)行擴(kuò)容,原來空間的數(shù)據(jù)不會(huì)發(fā)生變化。
原有的空間之后沒有足夠的空間
如果原來的空間之后沒有足夠的空間滿足需要,就會(huì)在內(nèi)存區(qū)域中開辟一片能夠滿足新的大小需要的連續(xù)空間,并且把原來空間的數(shù)據(jù)拷貝的新的空間中,釋放原來的空間,返回新的地址。
#include <stdio.h> int main() { int* ptr = (int*)malloc(100); if (ptr != NULL) { //業(yè)務(wù)處理 } else { exit(EXIT_FAILURE); } //擴(kuò)展容量 //代碼1 ptr = (int*)realloc(ptr, 1000); //代碼2 int* p = NULL; p = realloc(ptr, 1000); if (p != NULL) { ptr = p; } //業(yè)務(wù)處理 free(ptr); return 0; }
上述代碼種,代碼1的做法是不安全的,如果我們擴(kuò)容失敗后,會(huì)返回空指針,這個(gè)時(shí)候,ptr接收了空指針,就會(huì)造成ptr原來的那塊空間的數(shù)據(jù)丟失,并且造成內(nèi)存泄漏。
因此,一般我們都需要像代碼2那樣,先用一個(gè)臨時(shí)的指針變量接收返回值,并且在判定不為空指針后再?gòu)?fù)制給ptr。
動(dòng)態(tài)內(nèi)存管理中常見的錯(cuò)誤
我們?cè)谑股厦孢@些函數(shù)的時(shí)候,會(huì)經(jīng)常出現(xiàn)一下的錯(cuò)誤。
對(duì)NULL指針的解引用操作
void test() { int* p = (int*)malloc(INT_MAX / 4); //沒有進(jìn)行判空,就直接使用空間 *p = 20;//如果p的值是NULL,就會(huì)有問題 free(p); }
對(duì)動(dòng)態(tài)開辟空間的越界訪問
void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i <= 10; i++) { *(p + i) = i;//當(dāng)i是10的時(shí)候越界訪問 } free(p); }
對(duì)非動(dòng)態(tài)開辟內(nèi)存使用free釋放
void test() { int a = 10; int *p = &a; free(p); }
使用free釋放一塊動(dòng)態(tài)開辟內(nèi)存的一部分
void test() { int* p = (int*)malloc(100); p++; free(p);//p不再指向動(dòng)態(tài)內(nèi)存的起始位置 //這樣做一般編譯器也會(huì)報(bào)錯(cuò)的 }
對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放
void test() { int* p = (int*)malloc(100); free(p); free(p);//重復(fù)釋放,一般來說,編譯器會(huì)報(bào)錯(cuò) }
動(dòng)態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
void test() { int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); }
我們?cè)谑褂蒙厦娴暮瘮?shù)的時(shí)候,一定要小心,避免上面的這些錯(cuò)誤。
一些經(jīng)典的筆試題
題目1
void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
上面的代碼有錯(cuò),在開辟空間后,沒有進(jìn)行判空操作,并且在調(diào)用GetMemory函數(shù)的時(shí)候,傳入的形參是str的一份臨時(shí)拷貝,在函數(shù)內(nèi)部p的改變不會(huì)改變main中的str,所以在GetMemory返回的時(shí)候,p所指向的空間會(huì)泄漏,并且在strcpy中,造成了對(duì)空指針的解引用操作。
題目2
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); }
這段代碼也是錯(cuò)誤的,在GetMemory中,不是用動(dòng)態(tài)內(nèi)存管理的函數(shù)來開辟空間,而是使用數(shù)組的開辟方式,這樣的開辟方式會(huì)在棧區(qū)開辟空間,當(dāng)GetMemory函數(shù)調(diào)用完成的時(shí)候,就會(huì)銷毀開辟的數(shù)組,這時(shí)候,外面的str接收了返回的數(shù)組的地址,就會(huì)變成一個(gè)野指針。
題目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); }
上面的代碼在使用完成動(dòng)態(tài)開辟的空間后沒有進(jìn)行判空操作,并且沒有進(jìn)行內(nèi)存釋放,造成了內(nèi)存泄漏。
題目4
void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } }
上面的代碼提前釋放了空間,后面又使用指針對(duì)已經(jīng)釋放的空間進(jìn)行操作,這是非法的,釋放后,指向這塊空間的指針就是野指針,需要進(jìn)行置空。
柔性數(shù)組
C99 中,結(jié)構(gòu)中的最后一個(gè)元素允許是未知大小的數(shù)組,這就叫做『柔性數(shù)組』成員。
例如:
typedef struct st_type { int i; int a[0];//柔性數(shù)組成員 }type_a;
如果有的編譯器報(bào)錯(cuò),可以改成下面這個(gè)樣子
typedef struct st_type { int i; int a[];//柔性數(shù)組成員 }type_a;
柔性數(shù)組的特點(diǎn)
結(jié)構(gòu)中的柔性數(shù)組成員前面必須至少一個(gè)其他成員。
sizeof 返回的這種結(jié)構(gòu)大小不包括柔性數(shù)組的內(nèi)存。
包含柔性數(shù)組成員的結(jié)構(gòu)用malloc ()函數(shù)進(jìn)行內(nèi)存的動(dòng)態(tài)分配,并且分配的內(nèi)存應(yīng)該大于結(jié)構(gòu)的大小,以適應(yīng)柔性數(shù)組的預(yù)期大小。
例如:
//code1 typedef struct st_type { int i; int a[0];//柔性數(shù)組成員 }type_a; printf("%d\n", sizeof(type_a));//輸出的是4
typedef struct st_type { int i; int a[0];//柔性數(shù)組成員 }type_a; //代碼1 int i = 0; type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int)); //業(yè)務(wù)處理 p->i = 100; for (i = 0; i < 100; i++) { p->a[i] = i; } free(p);
這樣柔性數(shù)組成員a,相當(dāng)于獲得了100個(gè)整型元素的連續(xù)空間。
柔性數(shù)組的優(yōu)勢(shì)
上面的代碼也可以這樣設(shè)計(jì):
//代碼2 typedef struct st_type { int i; int* p_a; }type_a; type_a* p = (type_a*)malloc(sizeof(type_a)); p->i = 100; p->p_a = (int*)malloc(p->i * sizeof(int)); //業(yè)務(wù)處理 for (i = 0; i < 100; i++) { p->p_a[i] = i; } //釋放空間 free(p->p_a); p->p_a = NULL; free(p); p = NULL;
上述 代碼1 和 代碼2 可以完成同樣的功能,但是 方法1 的實(shí)現(xiàn)有兩個(gè)好處:
- 方便內(nèi)存釋放
- 這樣有利于訪問速度
到此這篇關(guān)于C語(yǔ)言動(dòng)態(tài)內(nèi)存管理分析總結(jié)的文章就介紹到這了,更多相關(guān)C語(yǔ)言 動(dòng)態(tài)內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理圖文詳解
- C語(yǔ)言深入細(xì)致講解動(dòng)態(tài)內(nèi)存管理
- 超詳細(xì)分析C語(yǔ)言動(dòng)態(tài)內(nèi)存管理問題
- C語(yǔ)言?超詳細(xì)梳理總結(jié)動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言?動(dòng)態(tài)內(nèi)存管理全面解析
- C語(yǔ)言的動(dòng)態(tài)內(nèi)存管理的深入了解
- 關(guān)于C語(yǔ)言動(dòng)態(tài)內(nèi)存管理介紹
- C語(yǔ)言動(dòng)態(tài)內(nèi)存管理介紹
- C語(yǔ)言動(dòng)態(tài)內(nèi)存管理深入探討
相關(guān)文章
Java?C++?算法題解leetcode652尋找重復(fù)子樹
這篇文章主要為大家介紹了Java?C++?算法題解leetcode652尋找重復(fù)子樹示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09C++ 實(shí)現(xiàn)靜態(tài)單鏈表的實(shí)例
這篇文章主要介紹了C++ 實(shí)現(xiàn)靜態(tài)單鏈表的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06C語(yǔ)言源碼實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言源碼實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12Mac OS上搭建Apache+PHP+MySQL開發(fā)環(huán)境的詳細(xì)教程
這篇文章主要介紹了Mac OS上搭建Apache+PHP+MySQL開發(fā)環(huán)境的詳細(xì)教程,包括常見的PHP連接MySQL失敗問題的解決辦法,需要的朋友可以參考下2016-01-01kernel劫持modprobe?path內(nèi)容詳解
這篇文章主要為大家介紹了kernel劫持modprobe?path的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05C語(yǔ)言通過gets和gets_s分別實(shí)現(xiàn)讀取含空格的字符串
在遇到包含空格的字符串輸入時(shí)該如何讀取呢?如果使用scanf以%s格式去讀取輸入的字符串,遇到空格就讀取結(jié)束了,顯然這樣是讀取不了的。本文就將介紹兩個(gè)可以對(duì)含空格字符串讀取的庫(kù)函數(shù)------gets和gets_s函數(shù),感興趣的可以了解一下2021-12-12