1. 棧區(qū):在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限,所以有時候會出現(xiàn)棧溢出的情況。 棧區(qū)主要存放運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)、返回地址等。
2. 堆區(qū):一般由程序員分配、釋放, 若程序員不釋放,程序結(jié)束時可能由操作系統(tǒng)回收,分配方式類似于鏈表。
3. 數(shù)據(jù)段 ( 靜態(tài)區(qū) static ) 存放全局變量、靜態(tài)數(shù)據(jù),程序結(jié)束后由系統(tǒng)釋放。
4. 代碼段:存放函數(shù)體(類成員函數(shù)和全局函數(shù))的二進(jìn)制代碼。
聲明在我們平時寫程序時,創(chuàng)建的變量都會在底層開辟內(nèi)存。C語言 的內(nèi)存布局分為三大塊:棧區(qū)、堆區(qū)、靜態(tài)區(qū)。其中棧區(qū)是我們平時使用最多的區(qū)域,棧區(qū)的使用習(xí)慣:先使用高地址處的空間,再使用低地址處的空間。然而棧區(qū)的使用總是有限的,所以本篇博客重點介紹如何使用堆區(qū)的內(nèi)存。
一、動態(tài)內(nèi)存管理函數(shù) 1. malloc 和 freemalloc - memory allocate - 分配一段連續(xù)的內(nèi)存空間
void* malloc (size_t size);
// 返回值:申請內(nèi)存成功,則返回開辟好空間的頭指針;申請內(nèi)存失敗,則返回 NULL
// 參數(shù):以字節(jié)為單位的申請值
free - 釋放并回收內(nèi)存
void free (void* ptr);
// 參數(shù):ptr 為先前用 malloc、calloc 或 realloc分配的內(nèi)存塊的頭指針
// 注意:一定要是頭指針,而不是中間的指針,否則就會出現(xiàn)釋放內(nèi)存不干凈的情況
使用示例#include#includeint main() { int* p = (int*) malloc(40); // 申請空間
if (p == NULL) {// 內(nèi)存分配失敗的情況
perror("malloc");
return 1;
}
for (int i = 0; i< 10; i++) {*(p + i) = i; // 為剛剛開辟的空間放入值
}
free(p); // free 將剛剛系統(tǒng)開辟的空間,再回收掉
p = NULL; // 為了防止 ptr 成為野指針,所以將其置成 NULL
return 0;
}
2. calloccalloc - 分配一段連續(xù)的內(nèi)存空間并清零
calloc 函數(shù) 和 malloc 函數(shù)的使用思想基本相同,只不過前者在分配內(nèi)存之后,又做了清零的步驟。
void* calloc (size_t num, size_t size);
// 返回值:申請內(nèi)存成功,則返回開辟好空間的頭指針;申請內(nèi)存失敗,則返回 NULL
// 參數(shù):num 表示元素個數(shù),size 表示每個元素的字節(jié)大小
使用示例#include#includeint main() {int* p = (int*) calloc(10, sizeof(int)); // 申請 10*4 = 40 個字節(jié)
if (p == NULL) {perror("calloc");
return 1;
}
for (int i = 0; i< 10; i++) {printf("%d ", *(p + i));
}
free(p); // 釋放
p = NULL;
return 0;
}
// 輸出結(jié)果:0 0 0 0 0 0 0 0 0 0
3. reallocvoid* realloc (void* ptr, size_t size);
使用 realloc 再次申請分配內(nèi)存的時候,需要指定重新開辟內(nèi)存的字節(jié)總大小。假設(shè)我們之前使用 malloc / calloc 申請內(nèi)存時,內(nèi)存不夠用了,realloc 就會派上用場。它可以做到很人性化地開辟內(nèi)存。
情況①:當(dāng) malloc / calloc 申請的內(nèi)存后面,有足夠的空間再次擴(kuò)充,realloc 就能夠直接接著上次的位置,繼續(xù)開辟。最終還是返回上一次開辟的原地址。
情況②:當(dāng) malloc / calloc 申請的內(nèi)存后面,沒有足夠的空間再次擴(kuò)充,realloc 也能夠另起一塊內(nèi)存區(qū)域,將之前申請的內(nèi)存中的數(shù)據(jù)也一并放入到新的內(nèi)存區(qū)域中。此時最終返回的是新的地址。
使用示例#include#includeint main() {int* p = (int*) malloc(40); // 1
if (p == NULL) {perror("malloc");
return 1;
}
for (int i = 0; i< 10; i++) {*(p + i) = i;
}
int* ptr = (int*)realloc(p, 80); // 2
if (ptr != NULL) { // 3
p = ptr;
}
free(p); // 4
p = NULL;
return 0;
}
// 1. malloc 申請分配內(nèi)存。
// 2. 假設(shè)空間不夠,使用 realloc 再次申請。
// 3. 由于內(nèi)存有限,所以 realloc 也有可能申請內(nèi)存失敗,當(dāng)失敗的時候就會返回 NULL. 只有當(dāng) ptr 不為空的時候,才能夠放入 p 中,這樣就能保證 p 不受影響。
// 4. 釋放內(nèi)存
4. 注意事項申請內(nèi)存分配后,始終需要記得使用 free. 當(dāng)我們不釋放動態(tài)申請的內(nèi)存的時候,如果程序結(jié)束,動態(tài)申請的內(nèi)存由操作系統(tǒng)自動回收;但如果程序不結(jié)束,動態(tài)內(nèi)存是不會自動回收的,就會形成內(nèi)存泄露的問題,久而久之,內(nèi)存就會被 " 耗干 "。
雖然現(xiàn)在的操作系統(tǒng)很智能,能夠限制一些程序的內(nèi)存使用,但還是建議標(biāo)準(zhǔn)化。
二、動態(tài)內(nèi)存管理函數(shù)使用時的錯誤示范 程序清單1動態(tài)內(nèi)存申請失敗,對 NULL 直接進(jìn)行了解引用操作。
#include#include#includeint main () {int* p = malloc(INT_MAX); // 申請失敗
*p = 100; // error
free(p);
p = NULL;
return 0;
}
程序清單2對動態(tài)開辟空間的越界訪問。
#include#includeint main() {int* p = malloc(40); // 開辟 40 個字節(jié)的內(nèi)存
if (p == NULL) {perror("malloc");
return 1;
}
// 越界訪問 41 ~ 44
for (int i = 0; i< 11; i++) {*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
程序清單3對非動態(tài)開辟內(nèi)存使用 free 釋放.
#include#includeint main() {int a = 10;
int* p = &a;
free(p); // error
p = NULL;
return 0;
}
程序清單4free 釋放不完整,或者 free 函數(shù)接收的指針不處于內(nèi)存開辟的頭部。
#include#includeint main() {int* p = (int*) malloc(100);
if (p == NULL) {perror("malloc");
return 1;
}
for (int i = 0; i< 10; i++) {*p = i;
p++;
}
free(p); // error
p = NULL;
return 0;
}
從第 40 個字節(jié)后面開始釋放內(nèi)存,那么前面的內(nèi)存就未被釋放完。
程序清單5對同一塊動態(tài)內(nèi)存多次釋放。
#include#includeint main() {int* p = malloc(100);
if (p == NULL) {perror("malloc");
return 1;
}
free(p);
free(p); // error
p = NULL;
return 0;
}
程序清單6內(nèi)存泄漏。
如下程序,在 test 函數(shù)中,即使我們寫了 free,但最終走不到釋放的代碼,依然會存在內(nèi)存泄漏的問題。所以說,free 代碼的放置位置很關(guān)鍵,并不是寫了 free 就一定能夠釋放動態(tài)內(nèi)存。
#include#includevoid test() {int* p = (int*)malloc(100);
if (1) {return;
}
free(p); // 代碼走不到這一行
p = NULL;
}
int main() {test();
return 0;
}
三、經(jīng)典的內(nèi)存筆試題
程序清單1#include#include#includevoid getMemory(char* p)
{p = (char*) malloc(100);
}
int main() {char* str = NULL;
getMemory(str);
strcpy(str, "hello world"); // 對空指針解引用
printf(str);
return 0;
}
分析:
在 getMemory 函數(shù)中,在堆區(qū)申請了 100 個字節(jié)的內(nèi)存大小,之后我們可以通過指針 p 來找到這些內(nèi)存。然而,在主函數(shù)中,str 始終為 NULL. 也就是說,str 并沒有指向任何一塊區(qū)域,所以后面在使用 strcpy 函數(shù)的時候," hello world " 就不能成功拷貝。
這題錯誤的主要原因就是 str 為實參,p 為形參,對形參的操作不影響實參。
程序清單2#include#include#includevoid getMemory(char** p, int num)
{*p = (char*) malloc(num); // 相當(dāng)于 str 指向了開辟的 100 個字節(jié)的空間
// *(&str)<==>str = (char*) malloc(100);
}
int main() {char* str = NULL;
getMemory(&str, 100);
strcpy(str, "hello");
printf(str);
free(str);
str = NULL;
return 0;
}
// 輸出結(jié)果: hello
分析:
程序清單2 算是 對 程序清單1 的優(yōu)化,在 getMemory 函數(shù)中,當(dāng)我們傳過去的是 str 的地址時,再次對二級指針解引用,就相當(dāng)于能夠通過 str 直接找到開辟的內(nèi)存。上面的程序沒有問題,但是我們也不要忘記 free 來幫助系統(tǒng)回收內(nèi)存了。
程序清單3#include#include#includechar* getMemory()
{char p[] = "hello world";
return p;
}
int main() {char* str = NULL;
str = getMemory();
printf(str);
return 0;
}
// 輸出結(jié)果:隨機(jī)值
分析:
數(shù)組 p 在棧區(qū)開辟了內(nèi)存,但出了 getMemory 函數(shù)后,數(shù)組中之前所占用的空間就被操作系統(tǒng)銷毀了,也就是說,剛剛棧區(qū)的內(nèi)存還給了系統(tǒng),現(xiàn)在出了函數(shù)后,即使知道這塊內(nèi)存的地址,但已經(jīng)沒有權(quán)限再次使用這塊區(qū)域了。因為數(shù)組 p 是一個臨時的局部變量。
p 表示數(shù)組的首元素地址,假設(shè) p 的地址為 0x11223344,那么出了 getMemory 函數(shù)后,現(xiàn)在 0x11223344 指向的內(nèi)存可能已經(jīng)被其他數(shù)據(jù)占用了。所以在主函數(shù)中,當(dāng) str 再次指向 0x11223344,找到的就是一些隨機(jī)存放的數(shù)據(jù)。
程序清單4#includeint* test() {int a = 10;
return &a;
// 出了 test 函數(shù),a 的地址將被系統(tǒng)回收
}
int main() {int* p = test();
return 0;
}
程序清單4 和 程序清單3 的錯誤思想基本相同,這是典型的 【返回棧空間地址、野指針】的問題。a 的地址出了 test 函數(shù)后,就需要被回收,所以后續(xù)在主函數(shù)中,指針 p 就沒有權(quán)限去操作這塊內(nèi)存區(qū)域。
為什么棧區(qū)的地址最后需要被回收?
答:因為函數(shù)和局部變量在使用時,需要在棧區(qū)開辟內(nèi)存,當(dāng)用完了,如果不被回收,那么操作系統(tǒng)的內(nèi)存遲早要被耗盡。
舉個例子:這就和住酒店一樣,當(dāng) 旅客 A 租完了房子,房子就需要打掃干凈,騰出來給后面的 旅客B / 旅客C / 旅客D… 使用。否則,房子永遠(yuǎn)不夠后面的旅客使用。如果 旅客A 前腳剛走,旅客B 就住進(jìn)去了,那么這個房子暫時屬于 旅客B 的,假設(shè) 旅客A 發(fā)現(xiàn)自己手機(jī)留在了剛剛的房子忘拿了,那么 旅客A 就需要和前臺報備一下,如果 旅客A 直接沖進(jìn)去了剛剛的房子找手機(jī),旅客B 就會告他非法訪問。
程序清單5#includevoid test(){char* str = (char*) malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL){
// 野指針,非法操作
strcpy(str, "world");
printf(str);
}
}
int main() {test();
return 0;
}
上面的代碼依舊是野指針問題,當(dāng) malloc 開辟的堆區(qū)內(nèi)存區(qū)域被 free 回收之后,str 指向的地址依舊是剛剛申請的頭部地址,但此時卻沒有了操作這塊內(nèi)存的權(quán)限。所以將 " world " 字符串放入 str 指向的區(qū)域,最終就會造成非法訪問。
綜上所述,我們應(yīng)該在 free 的后面添加如下代碼:
str = NULL; // 防止 str 成為一個野指針
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文標(biāo)題:C語言動態(tài)內(nèi)存管理-創(chuàng)新互聯(lián)
文章鏈接:http://jinyejixie.com/article0/dpogio.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷、微信公眾號、面包屑導(dǎo)航、ChatGPT、搜索引擎優(yōu)化、軟件開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容