【c語言】動態內存管理(超詳細)

在這里插入圖片描述
他治愈了身邊所有人,唯獨沒有治愈他自己—超脫

csdn上的朋友你們好呀!!今天給大家分享的是動態內存管理

👀為什么存在動態內存分配

在這里插入圖片描述

我們定義的局部變量在棧區創建

    int n = 4;//在棧上開辟4個字節大小int arr[10] = { 0 };//在棧上開辟連續的40個字節大小

上述變量創建的特點
1. 空間開辟大小是固定的。
2. 數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。
但是對于空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程序運行的時候才能知道,那數組的編譯時開辟空間的方式就不能滿足了

int main()
{int n;scanf("%d",&n);int arr[n];}

上述代碼只能在C99標準編譯器上才行,vs系列編譯器均不支持,那我們怎么才能在運行的時候,實現上述變長數組的代碼呢??這時候就只能試試動態存開辟了。

👀 動態內存函數的介紹

malloc

在這里插入圖片描述

函數功能:開辟內存塊
參數size_t:需要申請的字節數
返回值:申請失敗返回空指針,申請成功返回指向申請該空間首地址的指針
頭文件:stdlib.h
注意返回指針的類型是void*,這時候需要你把該指針強制類型轉化為你想要的類型,這樣方便訪問,以及解引用,malloc申請來的空間是連續的,但是多次malloc來的是不連續的


malloc的使用

int main()
{int*p=(int*) malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){*(p + i) = i;//對每一個四個字節大小的元素賦值,這里*(p+i)的本質就是p[i];printf("%d", *(p + i));//打印每個元素}return 0;//程序正常退出}

在這里插入圖片描述


free

在這里插入圖片描述

功能:釋放內存塊
參數:指針接收要釋放內存塊的首地址
頭文件:stdlib.h
返回值:無

了解了這些之后,我們試一下釋放剛才malloc來的內存塊

int main()
{int i = 0;int*p=(int*) malloc(40);if (p == NULL){perror("malloc:");return 1;}for (int i = 0; i < 10; i++){*(p + i) = i;printf("%d", *(p + i));}free(p);//指針接收要釋放內存塊的首地址p = NULL;//很有必要否則p為野指針return 0;}

在這里插入圖片描述
當p所指向的申請的空間釋放時,p指針指向隨機位置,p變成野指針。
如果我們不釋放動態內存申請的內存的時候,程序結束,動態申請內存由操作系統自動回收,如果不用free函數釋放申請好的空間,就會在程序運行結束前一直存在于堆中,造成內存泄漏

int main()
{while (1){malloc(1000);}return 0;}

在這里插入圖片描述
我是不知天高地厚的年輕人哈哈哈哈哈


calloc

在這里插入圖片描述

功能:申請一個數組在內存中,并且初始化為0;
參數:size_t num申請數組元素的個數,size_t size每個元素的字節大小
返回值:申請失敗返回空指針,申請成功返回指向申請該空間首地址的指針
頭文件:stdlib.h

calloc函數使用

int main()
{int i = 0;int*p=(int*) calloc(10,sizeof(int));//申請10個元素,每個元素字節大小4if (p == NULL)//如果返回空指針的話,申請失敗{perror("calloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印初始化的值}free(p);p = NULL;return 0;}

在這里插入圖片描述

malloc和calloc的區別:與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為全0
我們可以看看malloc有沒有先初始化

int main()
{int* p = (int*)malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印每個元素}free(p);//指針接收要釋放內存塊的首地址p = NULL;//很有必要否則p為野指針return 0;//程序正常退出}

在這里插入圖片描述

可以看到是未初始化的,放的隨機值


realloc

在這里插入圖片描述

功能:內存塊的擴容
參數:第一個參數接收要擴容內存塊的首地址,擴容后總字節大小(包括原來的字節大小)
頭文件:stdlib.h
返回值:在這里插入圖片描述
在這里插入圖片描述

realloc函數使用

int main()
{int* p = (int*)malloc(40);//申請了40個字節,強制轉化為int*類型指針if (p == NULL)//如果返回空指針的話,申請失敗{perror("malloc:");//打印錯誤信息return 1;//非正常退出}for (int i = 0; i < 10; i++)//循環打印擴容前的元素{*(p + i) = i;printf("%d ", *(p + i));}int* ptr = (int*)realloc(p, 80);//原空間夠用ptr==p,不夠用的話ptr存放新地址if (ptr != NULL)//擴容成功{p = ptr;//原空間夠用ptr==p,不夠用的話ptr存放新地址,重新將新地址給p}for (int i = 10; i < 20; i++)//擴容后新空間的{*(p + i) = i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}

編譯運行
在這里插入圖片描述


👀常見的動態內存錯誤

1.對NULL指針的解引用操作

int main()
{int* p = (int*)malloc(1000);int i = 0;//if (p ==NULL)//{//	return 1;//}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}

在這里插入圖片描述

當malloc申請內存失敗,p=NULL,i=0,相當于給空指針解引用

解決辦法:對malloc函數返回值做出判斷

int main()
{int* p = (int*)malloc(1000);int i = 0;if (p ==NULL){return 1;}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}

2. 對動態開辟空間越界訪問

int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <=25; i++)//越界訪問{*(p + i) = i;}free(p);p = NULL;return 0;
}

編譯運行
在這里插入圖片描述
解決方法:人為檢查是否越界

修改:

int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <25; i++)//=25變成<25{*(p + i) = i;}free(p);p = NULL;return 0;
}

3.對非動態開辟內存進行free

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

編譯運行
在這里插入圖片描述

解決方案:你別手賤(🙂)


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

int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}free(p);p = NULL;return 0;}

編譯運行
在這里插入圖片描述

解決方案:別改變p指向的地址,或者用一個指針記錄申請內存的首地址

plan1:

int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*(p+i)= i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;}

plan2:

int main()
{int* p = (int*)malloc(100);int* q = p;if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p= i;printf("%d ", *p);p++;}free(q);q = NULL;return 0;}

5.多次free已經釋放的內存

int main()
{int* p = malloc(40);if (p == NULL){return 1;}free(p);free(p);p = NULL;return 0;
}

編譯運行
在這里插入圖片描述
解決方案:別多次free已經釋放的內存(滑稽)


6.動態開辟內存忘記釋放

見上面


👀幾個經典的筆試題

1

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

分析:定義一個char*str,讓他指向空,str接收GetMemory()函數返回來的地址,進入GetMemory()函數,return p,只能把p[]的首地址傳回去,而p[]是局部變量,出GetMemory(),p[]銷毀,當你傳回去的時候,str接收的是野地址,str為野指針。打印不出來hello world
編譯運行:在這里插入圖片描述


2.

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

分析:在這里插入圖片描述

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);
}

分析:在這里插入圖片描述

但是沒有free釋放內存

運行編譯
在這里插入圖片描述


4.

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

分析:在這里插入圖片描述

運行編譯
在這里插入圖片描述


👀 柔性數組

也許你從來沒有聽說過柔性數組(flexible array)這個概念,但是它確實是存在的。 C99 中,結構中的最
后一個元素允許是未知大小的數組,這就叫做『柔性數組』成員

柔性數組的特點

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

我們可以定義一個結構體

struct pp {int a;int b[];};
int main()
{printf("%d", sizeof(struct pp));}

編譯運行
在這里插入圖片描述

確實沒有包括柔性數組大小

柔性數組的使用

struct pp {int a;int b[];//柔性數組成員
};
int main()
{struct pp* p = (struct pp*)malloc(sizeof(struct pp) + 10 * sizeof(int));//malloc中第一個元素大小+柔性數組字節大小p->a = 4;//賦值for (int i = 0; i < 10; i++){p->b[i] = i;//賦值}for (int i = 0; i < 10; i++){printf("%d ", p->b[i]);//打印柔性數組}printf("%d ", p->a);free(p);//free掉malloc來的空間p = NULL;//p置為空指針}

我們能不能用指針代替那個柔性數組呢,我們可以將指針指向的那個地方malloc來使用
定義一個結構體

struct pp {int a;int* p;
};

int main()
{struct pp* q = (struct pp*)malloc(sizeof(struct pp));//申請結構體大小的內存if (q == NULL)//判斷申請是否成功{return 1;//異常退出}q->p = (int*)malloc(10*sizeof(int));if (q->p == NULL)//判斷申請是否成功{return 1;//異常退出}q->a = 10;//賦值for (int i = 0; i < 10; i++){q->p[i]= i;賦值}printf("%d ", q->a);for (int i = 0; i < 10; i++){printf("%d ", q->p[i]);}free(q->p);//free掉p指針指向的另一塊申請空間的內存q->p = NULL;//指針置空,防止野指針free(q);//free掉q指向的申請的內存q = NULL;}

分析:

malloc過程在這里插入圖片描述
釋放過程
在這里插入圖片描述
編譯運行
在這里插入圖片描述

上述 代碼1 和 代碼2 可以完成同樣的功能,但是 方法1 的實現有兩個好處: 第一個好處是:方便內存釋放
如果我們的代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把整個結構體返回給用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,所以你不能指望用戶來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉。
第二個好處是:這樣有利于訪問速度.
連續的內存有益于提高訪問速度,也有益于減少內存碎片,根據局部性原理,連續存放的數據,cup從緩沖區讀取的快,緩存區從內存中讀取的快。

總結

本片分享了四個動態內存函數,以及常見動態內存錯誤,幾個經典的筆試題,以及柔性數組的概念,如果你覺得對你有幫助的話,希望能留下你的點贊,關注加收藏,如果有不對的地方,可以私信我,謝謝各位佬們!!!

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

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

相關文章

Android Socket使用TCP協議實現手機投屏

本節主要通過實戰來了解Socket在TCP/IP協議中充當的是一個什么角色&#xff0c;有什么作用。通過Socket使用TCP協議實現局域網內手機A充當服務端&#xff0c;手機B充當客戶端&#xff0c;手機B連接手機A&#xff0c;手機A獲取屏幕數據轉化為Bitmap&#xff0c;通過Socket傳遞個…

Excel設置某列或者某行不某行不可以編輯,只讀屬性

設置單元格只讀的三種方式: 1、通過單元格只讀按鈕&#xff0c;設置為只為 設置行或者列的只讀屬性&#xff0c;可以設置整行或者整列只讀 2、設置單元格編輯控件為標簽控件(標簽控件不可編輯) 3、通過鎖定行&#xff0c;鎖定行的修改。鎖定的行與只讀行的區別在于鎖定的行不…

電子商務環境下旅游價值鏈

邁克爾 ? 波特(Michael E. Porter)在其《競爭優勢》一書中提出了“價值鏈” 的概念&#xff0c;并認為一家企業最核心的競爭優勢在于對價值鏈的設計。雖然邁克爾 ? 波 特提出的價值鏈主要是針對企業內部的價值鏈&#xff0c;但他視價值鏈為一系列連續完成的 活動&#xff…

openGauss學習筆記-40 openGauss 高級數據管理-鎖

文章目錄 openGauss學習筆記-40 openGauss 高級數據管理-鎖40.1 語法格式40.2 參數說明40.3 示例 openGauss學習筆記-40 openGauss 高級數據管理-鎖 如果需要保持數據庫數據的一致性&#xff0c;可以使用LOCK TABLE來阻止其他用戶修改表。 例如&#xff0c;一個應用需要保證表…

GPT垂直領域相關模型 現有的開源領域大模型

對于ToC端來說&#xff0c;廣大群眾的口味已經被ChatGPT給養叼了&#xff0c;市場基本上被ChatGPT吃的干干凈凈。雖然國內大廠在緊追不舍&#xff0c;但目前絕大多數都還在實行內測機制&#xff0c;大概率是不會廣泛開放的&#xff08;畢竟&#xff0c;各大廠還是主盯ToB、ToG市…

C/C++ 注意點補充

C/C 注意點補充 函數缺省 函數缺省 https://blog.csdn.net/xinger_28/article/details/83898804 // 是的&#xff0c;C語言中的函數不支持直接定義缺省參數。在你提供的代碼中&#xff0c;函數DelayXms沒有定義缺省參數。缺省參數只在一些高級編程語言中&#xff08;如C&…

flutter

1.dart語言學習 dart在線編輯器 //第一段dart代碼 void main() {ceshi c new ceshi(1,2);print(c.right);c.right 2;print(c.right);print(c.bottom);c.bottom 4;print(c.bottom); }class ceshi {num left, top;ceshi(this.left, this.top);num get right > left top;…

視頻集中存儲安防監控平臺EasyCVR優化AI硬件接入時的通道顯示異常問題

安防視頻監控平臺視頻集中存儲EasyCVR可拓展性強、視頻能力靈活、部署輕快&#xff0c;可支持的主流標準協議有國標GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持廠家私有協議與SDK接入&#xff0c;包括海康Ehome、海大宇等設備的SDK等。 安防監控視頻云存儲平臺EasyCVR既具…

【Python國內源】pip換源終極方法【Windows】

1、為什么要pip換源下載 安裝第三方庫時&#xff0c;很多庫來自于國外&#xff0c;下載速度慢得感人&#xff01; 2、常見的國內源 https://pypi.tuna.tsinghua.edu.cn/simple #清華 http://mirrors.aliyun.com/pypi/simple/ #阿里云 https://pypi.mirrors.ustc.e…

go_細節注意

go細節 一、使用指針接受者和不使用指針接受者1&#xff0c;不使用指針接受者&#xff1a;2&#xff0c;使用指針接受者3&#xff0c;區別與優劣勢 一、使用指針接受者和不使用指針接受者 1&#xff0c;不使用指針接受者&#xff1a; func (d dog) move() {fmt.Println("…

使用Logstash將數據從MySQL同步至Elasticsearch(有坑)

文章目錄 一、準備工作1、安裝elasticSearchkibana2、安裝MySQL3、安裝Logstash 二、全量同步1、準備MySQL數據與表2、上傳mysql-connector-java.jar3、啟動Logstash4、修改logstash.conf文件5、修改full_jdbc.sql文件6、打開Kibana創建索引和映射7、重啟logstash進行全量同步8…

TCP/IP協議追層分析物理層(第三十九課)

TCP/IP協議追層分析物理層(第三十九課) 1 物理層:建立、維護、斷開物理連接,定義了接口及介質,實現了比特流的傳輸。 1、傳輸介質分類 有線介質:網線(雙絞線)、光纖 無線介質:無線電 微波 激光 紅外線 2、雙絞線分類: 五類cat5: 適用于100Mbps 超五類cat5e:適用于…

Qt掃盲- Graphics View框架理論綜述

Graphics View框架理論綜述 一、概述二、Graphics View 體系結構1. The Scene2. The View3. 圖元 Item 三、圖形視圖坐標系統1. 圖元Item的坐標2. Scene Scene坐標3. View 視圖坐標4. 坐標映射 四、關鍵特性1. 縮放和旋轉2. 打印3. 拖放4. 鼠標指針和 提示5. 動畫6. OpenGL渲染…

【100天精通python】Day35:一文掌握GUI界面編程基本操作

目錄 專欄導讀 1 GUI 編程概述 1.1 為什么需要GUI&#xff1f; 1.2 常見的GUI編程工具和庫 1.3 GUI應用程序的組成和架構 2 使用Tkinter 庫 進行GUI編程 2.1 使用Tkinter庫進行GUI編程的基本流程 2.2 使用Tkinter庫進行GUI編程 2.2.1 導入Tkinter庫 2.2.2 添加標簽和…

繪制世界地圖or中國地圖

寫在前面 在8月初,自己需要使用中國地圖的圖形,自己就此也查詢相關的教程,自己也做一下小小總結,希望對自己和同學們有所幫助。 最終圖形 這個系列從2022年開始,一直更新使用R語言分析數據及繪制精美圖形。小杜的生信筆記主要分享小杜學習日常!如果,你對此感興趣可以加…

Flutter Engine編譯環境安裝

前言 根據設置引擎開發環境的描述&#xff0c;確保有以下可用依賴項&#xff1a; Linux、macOS 或 Windows。 Linux 支持 Android 和 Fuchsia 的交叉編譯工件&#xff0c;但不支持 iOS。macOS 支持 Android 和 iOS 的交叉編譯工件。Windows 不支持任何 Android、Fuchsia 或 i…

MySQL存儲結構及索引

文章目錄 MySQL結構1.2存儲引擎介紹1.3存儲引擎特點InnoDB邏輯存儲結構 MyISAMMemory區別及特點存儲引擎選擇 索引索引概述索引結構BTreeHash索引分類聚集索引&二級索引索引語法SQL性能分析索引優化最左前綴法則范圍查詢字符串不加引號模糊查詢or連接條件數據分布影響覆蓋索…

達夢數據庫dbms_stats包的操作實踐記錄

索引的統計信息收集 GATHER_INDEX_STATSindex_stats_show 根據模式名&#xff0c;索引名獲得該索引的統計信息。用于經過 GATHER_TABLE_STATS、GATHER_INDEX_STATS 或 GATHER_SCHEMA_STATS 收集之后展示。返回兩個結果集&#xff1a;一個是索引的統計信息&#xff1b;另一個是…

Kotlin優點及為什么使用Kotlin

文章目錄 一 Hello Kotlin二 Kotlin優點三 團隊為什么采用 Kotlin 一 Hello Kotlin Kotlin和Andriod 二 Kotlin優點 三 團隊為什么采用 Kotlin

如何從PHP 獲取絕對路徑、文檔根目錄、基本 URL

根據您的服務器配置,獲取正確的路徑信息可能具有挑戰性。例如,PHP 并沒有直接提供一個變量來返回站點基本 URL。以下是一些代碼片段,可以幫助您獲取絕對路徑、文檔根目錄和基本 URL。 獲取絕對路徑 如果您的腳本位于 /path/directory/ 目錄中,您可以使用以下代碼片段來獲…