從malloc到free:動態內存管理全解析

1.為什么要有動態內存管理

我們已經掌握的內存開辟方法有:

int main()
{int val = 20;//在棧空間上開辟四個字節char arr[20] = { 0 };//在棧空間上開辟10個字節的連續空間return 0;
}

上述開辟的內存空間有兩個特點:

1.空間開辟的時候大小已經固定
2.數組在聲明的時候,必須指定數組的長度,數組空間一旦確定了大小就不能調整

但是對于所需內存空間的需求,不僅僅是上述的情況,有時候我們需要的空間大小在程序運行時才能知道,那數組就不能滿足我們的需求了。
C語言由此引入了動態內存開辟,讓程序員可以自己申請和釋放空間。

2.malloc和free

C語言提供了一個動態內存開辟的函數malloc,函數原型如下:

void* malloc(size_t size);

這個函數向內存申請?塊連續可?的空間,并返回指向這塊空間的指針。
? 如果開辟成功,則返回?個指向開辟好空間的指針。
? 如果開辟失敗,則返回?個 NULL 指針,因此malloc的返回值?定要做檢查。
? 返回值的類型是 void* ,所以malloc函數并不知道開辟空間的類型,具體在使?的時候使?者自己來決定。
? 如果參數 size 為0,malloc的?為是標準是未定義的,取決于編譯器。

malloc詳細解析網頁

C語言還提供了一個函數free,專門用來做動態內存的釋放和回收的,函數原型如下:

void* free(void* ptr);

free函數?來釋放動態開辟的內存。
? 如果參數 ptr 指向的空間不是動態開辟的,那free函數的?為是未定義的。
? 如果參數 ptr 是NULL指針,則函數什么事都不做。
malloc和free都聲明在 stdlib.h 頭?件中。

free詳細解析網頁

學完了兩個函數,我們來舉一個例子幫助大家理解:

int main()
{int* pz = (int*)malloc(10 * sizeof(int));//注意,malloc自己返回的指針類型是void*的,所以需要強制轉換為int*類型if (pz == NULL)//如果創建失敗,返回的將是空指針{perror("malloc");//打印錯誤,為什么創建失敗return 1;//結束程序}for (int i = 0; i < 10; i++){*(pz + i) = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", *(pz + i));}free(pz);//動態內存使用完我們應該主動進行回收pz = NULL;//回收之后,內存空間將會釋放,但是pz指針仍然會指向那個地址,我們需要將他置為空指針return 0;
}

創建成功
觀察結果,我們可以發現利用malloc創建的堆空間實現了與數組創建的棧空間一樣的效果,它們都是一塊連續的空間。上述是創建成功的示例,我們再來看一個失敗的案例:
創建失敗,返回空指針

3.calloc和realloc

C語言還提供了一個函數叫calloc,他也是用來實現動態內存分配的,函數原型如下:

void* calloc(size_t num, size_t size);

? 函數的功能是為 num 個??為 size 的元素開辟?塊空間,并且把空間的每個字節初始化為0。
? 與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為0

calloc詳細解析網頁

看代碼:

int main()
{int* pz = (int*)calloc(10, sizeof(int));if (pz == NULL){perror("calloc");return 1;}for (int i = 0; i < 10; i++){printf("%d ", *(pz + i));}free(pz);pz = NULL;return 0;
}

比較malloc和calloc
近乎一樣的代碼,打印的結果完全不同,這就是malloc和calloc的區別。

接下來我們要學的最后一個函數是realloc,realloc函數的出現讓動態內存管理更加靈活。有時會我們發現過去申請的空間太?了,有時候我們?會覺得申請的空間過?了,那為了合理的申請內存,我們?定會對內存的??做靈活的調整。那 realloc 函數就可以做到對動態開辟內存大小的調整。他的函數原型如下:

void* realloc (void* ptr,size_t size);

? ptr 是要調整的內存地址
? size 調整之后新??
? 返回值為調整之后的內存起始位置。
? 這個函數調整原內存空間??的基礎上,還會將原來內存中的數據移動到 新 的空間。

realloc詳細解析網頁

realloc在調整內存空間時存在兩種情況:
? 情況1:原有空間之后有?夠?的空間
? 情況2:原有空間之后沒有?夠?的空間
realloc分配內存是的兩種情況
情況1
當是情況1 的時候,要擴展內存就直接原有內存之后直接追加空間,原來空間的數據不發?變化。
情況2
當是情況2 的時候,原有空間之后沒有?夠多的空間時,擴展的?法是:在堆空間上另找?個合適??的連續空間來使?。這樣函數返回的是?個新的內存地址。原有空間的數值會被存入新的空間內。
由于上述的兩種情況,realloc函數的使?就要注意?些。

int main()
{int* pz1 = (int*)malloc(10 * sizeof(int));if (pz1 == NULL) {perror("malloc");return 1;}//業務處理//發現內存不夠,需要擴充//方法1//pz1 = (int*)realloc(pz1, 20 * sizeof(int));//方法2int* pz2 = NULL;pz2 = (int*)realloc(pz1, 20 * sizeof(int));if (pz2 == NULL){perror("realloc");free(pz1);return 1;}pz1 = pz2;//業務處理free(pz1);pz1 = NULL;return 0;
}

方法一存在下面風險:
內存泄漏風險:
若 realloc 失敗返回 NULL,原指針 pz1 會被直接覆蓋為 NULL。
后果:原內存(10個int的空間)徹底丟失,無法再被釋放,導致內存泄漏。

空指針操作風險: 未檢查返回值直接使用 pz1,若 realloc失敗,后續操作 pz1 的行為會引發未定義行為(如訪問空指針導致程序崩潰)。
使用方法二可以很好的避免發生這些錯誤。

4.常見的動態內存錯誤

4.1對NULL指針的解引用操作

int main()
{int* p = (int*)malloc(INT_MAX);*p = 10;printf("%d\n", *p);free(p);p = NULL;return 0;
}

上述代碼我們沒有檢查malloc函數創建失敗返回空指針的可能,實際上改代碼p返回的就是空指針,而我們對空指針進行解引用操作的行為時錯誤的,所以大家在利用malloc等函數申請內存時一定要檢查是否成功申請,否則返回的可能是空指針。

4.2對動態開辟空間的越界訪問

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}for (int i = 0; i < 12; i++){*(p + i) = i;//越界訪問}free(p);p = NULL;return 0;
}

上述代碼我們利用malloc函數像內存申請了40個字節的空間,但我們在for循環中卻訪問了48個字節的空間,在編譯時程序肯定是會崩潰的,這屬于越界訪問。

4.3對非動態開辟內存使用free釋放

int main()
{int a = 10;int* p = &a;free(p);p = NULL;return 0;
}

free函數只能釋放動態開辟的內存,他不能釋放棧空間的內存空間,上述代碼也會報錯。

4.4使用free釋放一塊動態開辟內存的一部分

int main()
{int* p = (int*)malloc(100);p++;free(p);p = NULL;return 0;
}

free函數不能這樣釋放內存,他的參數只能是開辟動態內存的起始地址,上述代碼也會在編譯時報錯。

4.5對同一塊動態內存多次釋放

int main()
{int* p = (int*)malloc(100);free(p);free(p);return 0;
}

free函數是不能對一塊動態內存進行重復釋放,編譯器會報錯。

4.6動態開辟內存忘記釋放(內存泄露)

void test()
{int* p = (int*)malloc(100);if (p != NULL){*p = 20;}
}int main()
{test();return 0;
}

上述代碼在運行時編譯器雖然不會直接報錯,但他是極不安全的,存在內存泄漏問題。

切記:動態開辟的空間?定要釋放,并且正確釋放。

5.動態內存經典筆試題分析

5.1題目1:

void GetMemory(char* p)
{p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}

上述代碼運行Test函數會產生什么樣的結果?
什么都不會打印。

看起來問題不大,其實錯漏百出:
指針傳遞問題(最核心問題): GetMemory函數接收的是char* p的副本(值傳遞), 函數內修改的是副本指針,不影響外部的str 導致str在Test函數中始終為NULL ,參考傳值調用
內存泄漏: malloc分配的內存沒有被釋放,而且由于指針問題,分配的內存甚至無法被訪問
空指針解引用: strcpy試圖向NULL指針寫入數據,會導致程序崩潰

我們這里提供兩種方法改進。
第一種,改用二級指針,類似傳址調用。
在這里插入圖片描述

第二種,函數返回指針。在這里插入圖片描述

exit(EXIT_FAILURE);包含在頭文件#include<stdlib.h>
exit() 函數:立即終止程序,清理緩沖區并關閉所有打開的文件。
EXIT_FAILURE:標準宏(通常值為1),表示程序異常終止。

5.2題目2:

char* GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}

打印結果:在這里插入圖片描述
這個代碼犯了一個很嚴重的錯誤,p數組他是一個局部變量,他只能在GetMemory函數內使用,出了該函數它的內存空間就被釋放掉了,該函數返回的也是懸空指針,建議使用動態分配堆內存代替數組。

> 記住,永遠不要返回局部變量的地址

5.3題目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);
}int main()
{Test();return 0;
}

上述代碼是可以打印正常結果的,但是還是我們上面所說的一些常見的動態內存錯誤,沒有檢查malloc返回的是否為空指針,沒有釋放動態開辟的內存,大家不要認為自己不會犯這種錯誤,作者反復提醒,望注意。

5.4題目4:

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

這個代碼同樣為檢查malloc函數創建失敗的可能,但這題還有更大的錯誤,str經free函數釋放后,變成了一個懸空指針,對懸空指針進行再操作的行為是未定義的(可能導致崩潰和數據損壞),所以我們應該養成釋放后即使置空指針的習慣。
在這里插入圖片描述

6.柔性數組

6.1柔性數組的介紹

在C99中,結構體的最后一個元素允許是未知大小的數組,這就叫柔性數組成員,如下:

struct st_type
{int i;int arr[];
};

柔性數組的特點:
? 結構中的柔性數組成員前?必須?少存在?個其他成員。
? sizeof 返回的這種結構??不包括柔性數組的內存。
? 包含柔性數組成員的結構?malloc ()函數進?內存的動態分配,并且分配的內存應該?于結構的??,以適應柔性數組的預期??。

看下面代碼:
在這里插入圖片描述

6.2柔性數組的使用

typedef struct st_type
{int i;int arr[];
}type_1;int main()
{type_1* pz = (type_1*)malloc(sizeof(type_1) + 20 * sizeof(int));pz->i = 100;for (int i = 0; i < 20; i++){pz->arr[i] = i;}free(pz);pz = NULL;return 0;
}

上述的代碼也可以設計成下面的形式:

typedef struct st_type
{int i;int* ps;
}type_1;int main()
{type_1* pz = (type_1*)malloc(sizeof(type_1));pz->i = 100;pz->ps = (int*)malloc(20 * sizeof(int));for (int i = 0; i < 20; i++){pz->ps[i] = i;}free(pz->ps);pz->ps = NULL;free(pz);pz = NULL;return 0;
}

上述代碼并沒有檢查malloc返回空指針的可能性,這是可以改進的一點。

除此以外,你認為代碼一和代碼二哪個更優秀呢?
代碼一更優秀,理由有二:
第?個好處是:?便內存釋放,如果我們的代碼是在?個給別??的函數中,你在??做了?次內存分配,并把整個結構體返回給??。??調?free可以釋放結構體,但是??并不知道這個結構體內的成員也需要free,所以你不能指望??來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存?次性分配好了,并返回給???個結構體指針,??做?次free就可以把所有的內存也給釋放掉。
第?個好處是:這樣有利于訪問速度,連續的內存有益于提?訪問速度,也有益于減少內存碎?。

加深結構體中成員數組與指針理解閱讀

7.總結C/C++語言程序內存區域劃分

在這里插入圖片描述

C/C++程序內存分配的?個區域:

  1. 棧區(stack):在執?函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執?結束時 這些存儲單元?動被釋放。棧內存分配運算內置于處理器的指令集中,效率很?,但是分配的內 存容量有限。棧區主要存放運?函數?分配的局部變量、函數參數、返回數據、返回地址等。
  2. 堆區(heap):?般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。分配? 式類似于鏈表。
  3. 數據段(靜態區)(static)存放全局變量、靜態數據。程序結束后由系統釋放。
  4. 代碼段:存放函數體(類成員函數和全局函數)的?進制代碼。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77660.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77660.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77660.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

健身房管理系統設計與實現(springboot+ssm+vue+mysql)含萬字詳細文檔

健身房管理系統設計與實現(springbootssmvuemysql)含萬字詳細文檔 健身房管理系統是一個全面的解決方案&#xff0c;旨在幫助健身房高效管理日常運營。系統主要功能模塊包括個人中心、會員管理、員工管理、會員卡管理、會員卡類型管理、教練信息管理、解聘管理、健身項目管理、…

seate TCC模式案例

場景描述 用戶下單時&#xff0c;需要創建訂單并從用戶賬戶中扣除相應的余額。如果訂單創建成功但余額劃扣失敗&#xff0c;則需要回滾訂單創建操作。使用 Seata 的 TCC 模式來保證分布式事務的一致性。 1. 項目結構 假設我們有兩個微服務&#xff1a; Order Service&#x…

【Linux】Rhcsa復習5

一、Linux文件系統權限 1、文件的一般權限 文件權限針對三類對象進行定義&#xff1a; owner 屬主&#xff0c;縮寫u group 屬組&#xff0c; 縮寫g other 其他&#xff0c;縮寫o 每個文件針對每類訪問者定義了三種主要權限&#xff1a; r&#xff1a;read 讀 w&…

《Operating System Concepts》閱讀筆記:p748-p748

《Operating System Concepts》學習第 64 天&#xff0c;p748-p748 總結&#xff0c;總計 1 頁。 一、技術總結 1.Transmission Control Protocol(TCP) 重點是要自己能畫出其過程&#xff0c;這里就不贅述了。 二、英語總結(生詞&#xff1a;3) transfer, transport, tran…

C語言之圖像文件的屬性

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 總有人間一兩風&#xff0c;填我十萬八千夢。 &#x1f680; 路漫漫其修遠兮&#xff0c;吾將上下而求索。 圖像文件屬性提取系統設計與實現 目錄 設計題目設計內容系統分析總體設計詳細設計程序實現…

opencv--基礎

opencv OpenCV是一個實現數字圖像處理和計算機視覺通用算法的開源跨平臺庫。 鏈接 opencv中的cv是什么意思 在OpenCV中&#xff0c;"cv" 是 "Computer Vision"&#xff08;計算機視覺&#xff09; 的縮寫。 opencv的實現語言 opencv的底層實現代碼是使…

Java創建對象的方式

1、通過new關鍵字創建新對象 用new關鍵字創建對象是我們在開發中最常用的方式&#xff0c;new關鍵字會為我們在堆內存中開辟一塊空間以存放對象的引用&#xff08;包含對象本身以及內部屬性的引用&#xff09;。 2、通過newInstance()方法創建新對象 newInstance()方法本質上是…

構建具備推理與反思能力的高級 Prompt:LLM 智能代理設計指南

在構建強大的 AI 系統&#xff0c;尤其是基于大語言模型&#xff08;LLM&#xff09;的智能代理&#xff08;Agent&#xff09;時&#xff0c;Prompt 設計的質量決定了系統的智能程度。傳統 Prompt 通常是簡單的問答或填空式指令&#xff0c;而高級任務需要更具結構性、策略性和…

豬行為視頻數據集

豬行為數據集包含 23 天(超過 6 周)的日間豬行為視頻,這些視頻由近乎架空的攝像機拍攝。視頻已配準顏色和深度信息。數據以每秒 6 幀的速度捕獲,并以 1800 幀(5 分鐘)為一批次進行存儲。大多數幀顯示 8 頭豬。 這里可以看到顏色和深度圖像的示例: 喂食器位于圖片底部中…

C++運算符重載詳解

C++ 中的運算符重載允許為用戶自定義類型(類或結構體)賦予運算符特定功能,使其操作更直觀。以下是運算符重載的關鍵點: 1. 基本語法 成員函數重載:運算符作為類的成員函數,左操作數為當前對象 (this),右操作數為參數。 class Complex {public:Complex operator+(const …

deep-share開源瀏覽器擴展,用于分享 DeepSeek 對話,使用戶能夠將對話內容保存為圖片或文本以便輕松分享

一、軟件介紹 文末提供程序和源碼下載學習 deep-share開源瀏覽器擴展&#xff0c;用于分享 DeepSeek 對話&#xff0c;使用戶能夠將對話內容保存為圖片或文本以便輕松分享。 二、軟件功能 One-click capture of DeepSeek chat content一鍵捕獲 DeepSeek 聊天內容Support sha…

Unity之如何實現RenderStreaming視頻推流

文章目錄 前言引入 UnityRenderStreaming 的好處教程步驟 1:設置環境步驟 2: 創建項目步驟 3:安裝軟件包步驟 5:下載示例步驟 6:檢查配置環境步驟 7:打開推流場景步驟 8: 準備用于流式傳輸的WebServer應用程序步驟 9: 運行 示例場景步驟 10:檢查視頻是否在瀏覽器中顯示…

30天開發操作系統 第26天 -- 為窗口移動提速

前言 昨天我們增加了可同時啟動的應用程序的數量&#xff0c;窗口也跟著變多了&#xff0c;整個畫面變得熱鬧起來。 話說&#xff0c;在對比color.hrb和color2.hrb的時候我們需要移動窗口&#xff0c;那個時候筆者感到窗口移動的速度很慢。在真機環境下的速度還算可以接受&…

9.QT-顯示類控件|Label|顯示不同格式的文本|顯示圖片|文本對齊|自動換行|縮進|邊距|設置伙伴(C++)

Label QLabel 可以?來顯??本和圖? 屬性說明textQLabel中的?本textFormat?本的格式.? Qt::PlainText 純?本? Qt::RichText 富?本(?持html標簽)? Qt::MarkdownText markdown格式? Qt::AutoText 根據?本內容?動決定?本格式pixmapQLabel 內部包含的圖?.scaledCo…

非參數檢驗題目集

非參數檢驗題目集 對醫學計量資料成組比較&#xff0c;相對參數檢驗來說&#xff0c;非參數秩和檢驗的優點是&#xff08; &#xff09; A. 適用范圍廣 B. 檢驗效能高 C. 檢驗結果更準確 D. 充分利用資料信息 E. 不易出現假陰性錯誤 對于計量資料的比較&#xff0c;在滿足參數…

libdxfrw庫使用總結

在 Win11VS2022CMake 平臺編譯 libdxfrw 庫的挑戰與應對 在當今數字化設計與開發領域&#xff0c;高效處理 CAD 文件格式如 DXF 是眾多項目的關鍵需求。libdxfrw 庫作為一種功能強大的工具&#xff0c;能助力開發者精準解析與寫入 DXF 文件&#xff0c;使其在眾多應用場景中備…

C++學習:六個月從基礎到就業——內存管理:RAII原則

C學習&#xff1a;六個月從基礎到就業——內存管理&#xff1a;RAII原則 本文是我C學習之旅系列的第十九篇技術文章&#xff0c;也是第二階段"C進階特性"的第四篇&#xff0c;主要介紹C中的RAII原則及其在資源管理中的應用。查看完整系列目錄了解更多內容。 引言 在…

【愚公系列】《Python網絡爬蟲從入門到精通》056-Scrapy_Redis分布式爬蟲(Scrapy-Redis 模塊)

&#x1f31f;【技術大咖愚公搬代碼&#xff1a;全棧專家的成長之路&#xff0c;你關注的寶藏博主在這里&#xff01;】&#x1f31f; &#x1f4e3;開發者圈持續輸出高質量干貨的"愚公精神"踐行者——全網百萬開發者都在追更的頂級技術博主&#xff01; &#x1f…

PyTorch基礎筆記

PyTorch張量 多維數組&#xff1a;張量可以是標量&#xff08;0D&#xff09;、向量&#xff08;1D&#xff09;、矩陣&#xff08;2D&#xff09;或更高維的數據&#xff08;3D&#xff09;。 數據類型&#xff1a;支持多種數據類型&#xff08;如 float32, int64, bool 等&a…

OSCP - Proving Grounds - Sar

主要知識點 路徑爆破cronjob 腳本劫持提權 具體步驟 依舊nmap 開始,開放了22和80端口 Nmap scan report for 192.168.192.35 Host is up (0.43s latency). Not shown: 65524 closed tcp ports (reset) PORT STATE SERVICE VERSION 22/tcp open ssh Open…