動態內存分配及管理——C語言

目錄

一、為什么存在動態內存分配

二、動態內存函數介紹

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

三、常見的動態內存錯誤

3.1 對NULL指針的解引用操作

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

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

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

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

3.6 動態開辟內存忘記釋放(內存泄漏)


????????今天,博主給大家帶來的是動態內存分配的學習和講解。在之前,我們學習了通訊錄,文章中利用到一些動態內存分配的一些知識,有些可能大家會看不懂,那么相信通過今天的這篇文章,大家的問題就會迎刃而解。本篇,我們將從“為什么存在內存分配”,“動態內存函數介紹”,以及“常見的動態內存錯誤”三個板塊來為大家一 一解答。

一、為什么存在動態內存分配

在之前,我們學過的內存開辟有哪些呢?比如,創建一個變量,或者創建一個數組。

    int a = 10;//在棧空間開辟四個字節char arr[10] = { 0 };//在棧空間開辟十個字節的連續空間

上面兩種開辟方式是我們常用的開辟內存的方式,但是這兩種開辟內存空間的方式有兩個特點:

① 空間開辟大小是固定的

②?數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。

但是由于空間的需求,有時候我們需要空間的大小在程序運行的時候才能知道,那數組的編譯時開辟空間的方式就不能滿足我們的需求了,這時就要試試動態內存開辟的方式了。

二、動態內存函數介紹

在學習動態內存函數之前,我們需要知道動態內存開辟的空間是放在堆區的,如上圖所示,局部變量和形式參數占用的空間是在棧區的,全局變量以及靜態變量開辟的空間是在靜態區的。?

2.1 malloc

C語言提供了一個動態內存開辟函數

void* malloc? (?? size_t? ? size?? ) ;
這個函數向內存申請一塊 連續可用 的空間,并返回指向這塊空間的指針。
① 如果開辟成功,則返回一個指向開辟好空間的指針。
② 如果開辟失敗,則返回一個NULL指針,因此 malloc 的返回值一定要做檢查。
③ 返回值的類型是 void* ,所以 malloc 函數并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
④ 如果參數 size 為 0 malloc 的行為是標準是未定義的,取決于編譯器。
舉個代碼例子來解釋吧!
int main()
{int* p = (int*)malloc(40);//開辟40個字節的空間if (p == NULL){perror("malloc");return 1;}//開辟成功for (int i = 0; i < 10; i++){printf("%d\n", *(p + i));}return 0;
}
因為返回的類型是void*,所以我們要根據自己的需求來進行強制類型轉換,其次,我們需要判斷是否開辟成功,如果返回值為NULL指針,那么就結束了,反之則是開辟成功。然后我們打印一下看看開辟成功的空間里面是什么。

此時我們發現開辟的空間里面存的是一堆沒見過的隨機數數,其實是malloc函數申請的空間,在申請成功后,直接返回這片空間的起始地址,不會初始化空間的內容。?

2.2 free

C 語言提供了另外一個函數 free ,專門是用來做動態內存的釋放和回收的,函數原型如下:
void free (? void*?? ptr? ) ;
上面我們學習了malloc函數,我們發現,malloc只負責申請空間,那么申請的這個空間當我們使用完之后會怎么樣呢?其實這塊空間并不會主動的還給操作系統,除非程序結束,否則這塊空間將會一直存在堆區。這個時候就需要另一個內存函數了。
總的來說: malloc申請的內存空間,當程序退出時,還給操作系統,當程序不退出時,動態申請的內存不會主動釋放。需要free函數來釋放空間。
具體怎么使用呢,直接把我們動態開辟的空間的起始地址傳入free函數中即可,代碼如下圖所示:
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//開辟成功free(p); //釋放開辟的空間p = NULL;  //置空return 0;
}

注意:p本來指向的空間被釋放后,p就變成野指針了,比較危險,這時候我們需要主動將p置為空指針。

free只能釋放動態開辟的空間,不能釋放靜態區或者棧區開辟的空間(標準未定義)?如果參數 ptr 指向的空間不是動態開辟的,那free函數的行為是未定義的。

② 如果參數 ptr NULL 指針,則函數什么事都不做。
malloc和free都聲明在 stdlib.h 頭文件中

2.3 calloc

C 語言還提供了一個函數叫 calloc calloc 函數也用來動態內存分配。原型如下:
void*?? calloc (? size_t?? num? ,?? size_t?? size? ) ;
① 函數的功能是為 num 個大小為 size 的元素開辟一塊空間 ,并且把空間的每個字節初始化為 0
② 與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為全 0
舉個例子吧:
int main()
{int* p = (int*)calloc(10, sizeof(int));//開辟十個連續的sizeof(int)大小的空間if (p == NULL){perror("calloc");return 1;}//打印數據int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}//釋放free(p);p = NULL;return 0;
}

當我們打印完之后,發現calloc會把每個字節初始化為0。

總的來說:calloc函數和malloc函數很相似,功能也是一樣,唯一不同的就是,會把開辟的每個字節都初始化為0。

????????所以如何我們對申請的內存空間的內容要求初始化,那么可以很方便的使用calloc 函數來完成任務。

2.4 realloc

realloc 函數的出現讓動態內存管理更加靈活。
????????有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時候內存,我們一定會對內存的大小做靈活的調整。那 realloc 函數就可以做到對動態開辟內存大小的調整。
函數原型如下:
void*?? realloc? (? void*?? ptr? ,?? size_t?? size? ) ;
① ptr 是要調整的內存地址(也就是被調整空間的起始地址,這塊空間之前已經開辟好了)
如果ptr為空指針,那么它的功能和malloc就是一樣的,開辟一個新的空間。
② size 調整之后新大小 (需要調整的新的空間的大小)
③ 返回值為調整之后的內存起始位置。
這個函數調整原內存空間大小的基礎上,還會將原來內存中的數據移動到 的空間。
④ realloc 在調整內存空間的是存在兩種情況:
情況1 :原有空間之后有足夠大的空間
情況2: 原有空間之后沒有足夠大的空間
情況1
當是情況1 的時候,要擴展內存就直接原有內存之后直接追加空間,原來空間的數據不發生變化。
情況2
當是情況2 的時候,原有空間之后沒有足夠多的空間時,擴展的方法是:在堆空間上另找一個合適大小的連續空間來使用,同時把原來空間的內容拷貝過來,然后自動釋放原來的內存空間,這樣函數返回的是一個新的內存地址。
由于上述的兩種情況,realloc函數的使用就要注意一些,我們這里用代碼來舉例子吧:
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//初始化為1~10int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}//增加一些空間int* ptr = (int*)realloc(p, 80);if (ptr != NULL)  //開辟成功{p = ptr;ptr = NULL;}else  //開辟失敗{perror("realloc");return 1;}//開辟成功,打印數據for (i = 0; i < 20; i++){printf("%d ", p[i]);}free(p);  //釋放空間p = NULL;  //置空return 0;
}

注意:考慮到可能開辟失敗,所以我們需要先進行判斷一下,如果開辟成功,則將返回的指針賦值給p來維護。

當我們開辟完之后,打印一下看看效果。

當我們使用完動態開辟的內存之后,仍然需要手動去釋放內存空間。

?當然,上面情況只是減少增加空間,如果要減少空間就比較簡單了,直接在原來的基礎上減少,地址返回的也是原來的地址。

好了,到這里動態內存管理的基本知識就介紹清楚了,實際上把這四個內存函數了解清楚,基本上對動態內存的知識點也就基本掌握了。接下來,我們來了解一下動態內存管理常見的內存錯誤,通過解釋這些錯誤,來更清楚更深入的了解動態內存管理。

三、常見的動態內存錯誤

3.1 對NULL指針的解引用操作

當我們動態內存開辟的時候,會存在開辟失敗的情況,此時返回的就是空指針。

如下代碼就是一個典型的例子:

void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就會有問題free(p);
}

此時動態內存開辟可能失敗了,就導致返回的指針為空指針,也就是p為空指針,如果再對p這個空指針進行解引用操作,那么就會報錯 。為了解決這種問題,我們在平時寫代碼的時候,為了避免空指針異常,要對動態開辟的空間返回的指針進行空指針判斷檢查。(好習慣)

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

這里直接拿代碼來解釋吧!

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;//當i是10的時候越界訪問}free(p);
}

這段代碼中,我們使用malloc開辟40個字節大小的空間,然后進行空指針檢查判斷,緊接著,在我們賦值的時候,我們最多只能訪問到p[9]這塊空間,代碼中我們i的最大值為10,此時很明顯,就出現了越界訪問。

總的來說:開辟多少空間,就只能使用多少空間,沒有開辟的,屬于操作系統的空間,我們不可以隨便進行操作訪問。

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

void test()
{int a = 10;int *p = &a;free(p);//ok?
}

在前面講free的時候說過,free釋放的空間,只能是動態內存開辟的空間,不能是靜態區或者棧區開辟的空間,上面代碼的例子中,a是局部變量,不是動態開辟的空間,所以不能被free釋放。

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*p = i + 1;p++;  //此時p不再指向malloc開辟的空間的起始地址}//釋放free(p);p = NULL;return 0;
}

上面代碼中,我們使用malloc開辟一塊空間,然后將起始地址返回給p,也就是說p指向molloc動態開辟的空間的起始地址,緊接著進行空指針檢查判斷,然后給p指向的空間進行賦值,但是,在賦值的過程中,p的指向發生了變化(如下圖),不再指向malloc開辟的空間的起始地址,此時在對p指向的空間進行釋放,這種做法是不被允許的,會報錯。

總結:free中的參數只能是動態內存開辟空間的起始地址,對于動態開辟的空間的起始地址不要隨便的更改指向。

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

void test()
{int *p = (int *)malloc(100);free(p);free(p);//重復釋放
}

上面這種情況也是一直很低級的錯誤,也是不被允許的,會報錯,最好的解決辦法就是,在釋放完之后,將p指針置為空,這樣在后面多次釋放也不影響,因為p已經是空指針了。

總結:不能對同一塊動態內存多次釋放,解決辦法:在第一次釋放完之后,將指針置為空指針(好習慣)

3.6 動態開辟內存忘記釋放(內存泄漏)

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

對于上面這個代碼,在test這個函數種,我們開辟了100個字節的動態空間,但是在最后,我們并沒有對這塊空間進行free釋放,這時候當我們跳出函數之后,指針變量p也銷毀了,這個時候,p原來指向的空間我們根本找不到了,但是這塊空間仍然存在,仍然被占用,只是我們丟失了它的起始地址,不能再對這塊空間進行操作或者訪問了,這就造成了這塊空間仍然存在占用內存,但是我們卻訪問不到,并無法釋放,這就是所謂的內存泄露。

針對這個問題的解決:在我們使用完動態空間之后,一定要進行free釋放。

總結:動態開辟的空間一定要釋放,并且正確釋放(切記)

好了,今天的動態內存分配和管理講到這里就結束了,聽到這里,相信大家的一些關于動態內存分配管理的問題就會迎刃而解了吧。如果哪里有問題,歡迎在評論區留言。如果覺得小編寫的還不錯的,那么可以一鍵三連哦,您的關注點贊和收藏是對小編最大的鼓勵。謝謝大家!!!

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

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

相關文章

搭建Web服務器并用cpolar發布至公網訪問

本地電腦搭建Web服務器并用cpolar發布至公網訪問 文章目錄 本地電腦搭建Web服務器并用cpolar發布至公網訪問前言1. 首先在電腦安裝PHPStudy、WordPress、cpolar2. 安裝cpolar&#xff0c;進入Web-UI界面3. 安裝wordpress4. 進入wordpress網頁安裝程序5. 利用cpolar建立的內網穿…

TensorFlow2.1 模型訓練使用

文章目錄 1、環境安裝搭建2、神經網絡2.1、解決線性問題2.2、FAshion MNIST數據集使用 3、卷積神經網絡3.1、卷積神經網絡使用3.2、ImageDataGenerator使用3.3、貓狗識別案例3.4、參數優化 1、環境安裝搭建 鏈接: Windows 安裝Tensorflow2.1、Pycharm開發環境 2、神經網絡 1…

【數據結構】堆(Heap)

一、堆的概念及結構 1、概念 堆(Heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵 完全二叉樹的 數組對象。 堆是非線性數據結構&#xff0c;相當于一維數組&#xff0c;有兩個直接后繼。 如果有一個關鍵碼的集合K { k?&#xff0c;k?&#xff0c…

“深入解析JVM:探索Java虛擬機的內部工作原理“

標題&#xff1a;深入解析JVM&#xff1a;探索Java虛擬機的內部工作原理 摘要&#xff1a;本文將深入解析Java虛擬機&#xff08;JVM&#xff09;的內部工作原理&#xff0c;包括類加載、內存管理、垃圾回收、即時編譯等關鍵概念。通過對這些概念的詳細講解和示例代碼的演示&a…

關于openfeign調用時content-type的問題

問題1描述&#xff1a; 今天在A服務使用openfeign調用B服務的時候&#xff0c;發現經常會偶發性報錯。錯誤如下&#xff1a; 情況為偶發&#xff0c;很讓人頭疼。 兩個接口如下&#xff1a; A服務接口&#xff1a; delayReasonApi.test(student);就是使用openfeign調用B服務的…

K8S常用命令

1.1 查看集群信息&#xff1a; kubectl cluster-info: 顯示集群信息。 kubectl config view: 顯示當前kubectl配置信息。1.2 查看資源狀態&#xff1a; kubectl get pods: 查看所有Pod的狀態。 kubectl get deployments: 查看所有部署的狀態。 kubectl get services: 查看所有…

Php“牽手”shopee商品詳情頁數據采集方法,shopeeAPI接口申請指南

shopee詳情接口 API 是開放平臺提供的一種 API 接口&#xff0c;它可以幫助開發者獲取商品的詳細信息&#xff0c;包括商品的標題、描述、圖片等信息。在電商平臺的開發中&#xff0c;詳情接口API是非常常用的 API&#xff0c;因此本文將詳細介紹詳情接口 API 的使用。 一、sh…

Python接口自動化之request請求封裝

我們在做自動化測試的時候&#xff0c;大家都是希望自己寫的代碼越簡潔越好&#xff0c;代碼重復量越少越好。那么&#xff0c;我們可以考慮將request的請求類型&#xff08;如&#xff1a;Get、Post、Delect請求&#xff09;都封裝起來。這樣&#xff0c;我們在編寫用例的時候…

Python文件操作教程,Python文件操作筆記

文件的打開與關閉 想一想&#xff1a; 如果想用word編寫一份簡歷&#xff0c;應該有哪些流程呢&#xff1f; 打開word軟件&#xff0c;新建一個word文件寫入個人簡歷信息保存文件關閉word軟件 同樣&#xff0c;在操作文件的整體過程與使用word編寫一份簡歷的過程是很相似的…

爬蟲逆向實戰(十三)--某課網登錄

一、數據接口分析 主頁地址&#xff1a;某課網 1、抓包 通過抓包可以發現登錄接口是user/login 2、判斷是否有加密參數 請求參數是否加密&#xff1f; 通過查看“載荷”模塊可以發現有一個password加密參數&#xff0c;還有一個browser_key這個可以寫死不需要關心 請求頭…

【11】Redis學習筆記 (微軟windows版本)【Redis】

注意:官redis方不支持windows版本 只支持linux 此筆記是依托微軟開發windows版本學習 一、前言 Redis簡介&#xff1a; Redis&#xff08;Remote Dictionary Server&#xff09;是一個開源的內存數據結構存儲系統&#xff0c;它也被稱為數據結構服務器。Redis以鍵值對&am…

取證的學習

Volatility命令語法 1.判斷鏡像信息&#xff0c;獲取操作系統類型 Volatility -f xxx.vmem imageinfo 在查到操作系統后如果不確定可以使用以下命令查看 volatility - f xxx.vmem --profile [操作系統] volshell 2.知道操作系統類型后&#xff0c;用–profile指定 volat…

IO和文件系統性能分析工具

以下內容來自于RHEL 官方文檔。以下工具可以用來分析磁盤 IO 和文件系統性能瓶頸。 分析方法見 《性能分析方法-《性能之巔》筆記》&#xff0c;USE 法必須要使用相關性能分析工具。 影響 IO 和文件系統性能的主要因素&#xff1a; 數據寫入或讀取特征 順序或隨機 buffered 或…

基于ssm+mysql智能圖書館導航系統設計與實現

摘 要 電腦的出現是一個時代的進步&#xff0c;不僅僅幫助人們解決了一些數學上的難題&#xff0c;如今電腦的出現&#xff0c;更加方便了人們在工作和生活中對于一些事物的處理。應用的越來越廣泛&#xff0c;通過互聯網我們可以更方便地進行辦公&#xff0c;也能夠在網上就能…

【Oracle 數據庫 SQL 語句 】積累1

Oracle 數據庫 SQL 語句 1、分組之后再合計2、顯示不為空的值 1、分組之后再合計 關鍵字&#xff1a; grouping sets &#xff08;&#xff08;分組字段1&#xff0c;分組字段2&#xff09;&#xff0c;&#xff08;&#xff09;&#xff09; select sylbdm ,count(sylbmc) a…

神經網絡基礎-神經網絡補充概念-20-激活函數

概念 激活函數是神經網絡中的一個重要組成部分&#xff0c;它引入了非線性性質&#xff0c;使得神經網絡可以學習和表示更復雜的函數關系。激活函數對于將輸入信號轉換為輸出信號起到了關鍵作用&#xff0c;它在神經元的計算過程中引入了非線性變換。 幾種常見的激活函數及其…

DR模式 LVS負載均衡群集

數據包流向分析&#xff1a; &#xff08;1&#xff09;客戶端發送請求到 Director Server&#xff08;負載均衡器&#xff09;&#xff0c;請求的數據報文&#xff08;源 IP 是 CIP,目標 IP 是 VIP&#xff09;到達內核空間。 &#xff08;2&#xff09;Director Server 和 Re…

Docker 網絡

目錄 Docker 網絡實現原理 Docker 的網絡模式&#xff1a; 網絡模式詳解&#xff1a; 1&#xff0e;host模式 2&#xff0e;container模式 3&#xff0e;none模式 4&#xff0e;bridge模式 5&#xff0e;自定義網絡 Docker 網絡實現原理 Docker使用Linux橋接&#x…

Linux下如何修改CPU 電源工作模式

最近處理一起歷史遺留問題&#xff0c;感覺很爽。 現象&#xff1a; 背景&#xff1a;設備采用ARM&#xff0c;即rk3568處理器&#xff0c;采用Linux系統&#xff1b;主要用于視覺后端處理 現象&#xff1a;當軟件運行一段時間&#xff0c;大概1個小時&#xff08;也不是很固定…

考研算法第46天: 字符串轉換整數 【字符串,模擬】

題目前置知識 c中的string判空 string Count; Count.empty(); //正確 Count ! null; //錯誤c中最大最小宏 #include <limits.h>INT_MAX INT_MIN 字符串使用發運算將字符加到字符串末尾 string Count; string str "liuda"; Count str[i]; 題目概況 AC代碼…