【C語言進階】動態內存管理及柔性數組

動態內存的開辟在C語言中相當重要的知識


1、為什么會存在動態內存分配

內存的開辟方式:

int a=20;//在棧空間上開辟4個字節

int arr[10];//在棧空間上開辟40個字節的連續空間

這種開辟空間的方式有兩個特點:

1、開辟的空間大小是固定的

2、數組在聲明的時候,必須指定數組長度,它需要的內存在編譯時分配。

但是這種內存開辟的方式存在缺陷,比如我們在寫通訊錄管理系統時指定了100個元素,但當我們填入元素過多時空間會不夠用,當聯系人較少時,又會產生空間的浪費,所以我們可以用一種靈活的方式,用多少提供多少,這種方式即動態內存管理。

2、動態內存函數的介紹

2.1malloc和free

void * malloc(size_t? size);

·?如果分配成功則返回指向被分配內存的指針,否則返回空指針NULL。

·?返回類型為void*,所以malloc函數并不知道開辟空間的類型,具體在使用時侯使用者自己來決定。

·?如果參數size為0,malloc的行為是標準為定義的,取決于編譯器。

free是專門用來做動態內存的釋放和回收的:

void free(void* ptr)

·?如果參數ptr指向的空間不是動態開辟的,那么free的行為是未定義的

·?如果參數ptr是NULL指針,則函數什么事都不做

malloc函數與free函數的聲明都在stdlib.h中

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
//malloc函數的使用
int main()
{int arr[10] = { 0 };//動態內存分配int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//說明開辟成功,使用內存int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}//free(p);//p=NULL;return 0;
}
//沒有free,并不是說內存空間就不回收了,當程序退出的時候,系統會自動回收內存空間的

注意:在使用malloc開辟空間時,使用完成一定要釋放空間,否則可能導致內存泄漏。?

內存泄漏:是指程序動態分配內存后,未能及時釋放這些內存,導致系統無法再為其他對象分配內存,或者可能導致系統內存耗盡的現象。

?2.2calloc

calloc函數也用來動態內存分配

void * calloc(size_t num,size_t size);

·????????函數的功能是為num個大小為size的元素開辟一塊空間,并且把空間的每個字節初始化為0.

·????????與函數malloc的區別在于calloc會在返回地址之前把申請的空間的每個字節初始化為全0。

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){printf("%s\n", strerror(errno));return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}//calloc與malloc的最大區別就是calloc會在返回地址之前把申請的空間的每個字節初始化為全0。

2.3realloc?

·????????realloc函數的出現讓動態內存管理更加靈活

·????????有時我們會發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,為了合理的分配內存,我們一定會對內存的大小做靈活地調整。那么realloc函數就可以做到對動態開辟內存大小的調整。

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

·????????ptr是要調整的內存地址

·??????? size是調整后的新大小?

·????????返回為調整之后的內存起始位置。

·????????這個函數調整原內存空間大小的基礎上,還會將原來內存中的數據移動到新空間

·????????realloc在調整內存空間存在兩種情況:

1、原有空間之后有足夠大的空間;(直接追加)

2、原有空間之后沒有足夠大的空間;(會覆蓋其他數據,所以開辟一塊更大的空間,把原有數據拷貝過去)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}int i = 0;for (i = 0; i < 10; i++){*(p + i) = i+1;}//擴容int* ptr = (int*)realloc(p, 80);//不能放在p中,因為若是給的數字過大,無法申請空間,從而返回空指針,會導致p原來的數據丟失{if (ptr != NULL)//擴容成功{p = ptr;}}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p=NULL;return 0;
}

3、常見的動態內存錯誤

3.1對NULL指針解引用操作

int main()
{
?? ?int* p = (int*)malloc(40);//應該判斷p是否為空指針,否則空間可能開辟失敗
?? ?*p = 20;
?? ?return 0;
}

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

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

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

int main()
{
?? ?int a = 10;
?? ?int* p = &a;
?? ?free(p);
?? ?return 0;//這個代碼會崩潰因為p所指向的空間是在棧區開辟的,并不是動態開辟的(堆區),不能進行釋放
}

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

int main()
{
?? ?int* p = (int*)malloc(40);
?? ?if (p == NULL)
?? ?{
?? ??? ?return 1;
?? ?}
?? ?int i = 0;
?? ?for (i = 0; i < 10; i++)
?? ?{
?? ??? ?*p = i;
?? ??? ?p++;
?? ?}
?? ?free(p);
?? ?p = NULL;//因為p的位置會發生改變不再是起始空間的地址,釋放僅僅釋放了一部分
?? ?return 0;
}

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

int main()
{
?? ?int* p = (int*)malloc(40);
?? ?if (p == NULL)
?? ?{
?? ??? ?return 1;
?? ?}
?? ?//.....
?? ?free(p);//p一旦釋放完后,所指向的空間已經回收,但p依然會記得地址,此時p就相當于一個野指針相當危險
?? ?//.....
?? ?free(p);
?? ?return 0;
}

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

//情形一:

void test()
{
?? ?int* p = (int*)malloc(40);
?? ?//........
?? ?int x = 0;
?? ?scanf("%d", &x);//如果在這里用戶輸入1,是會直接返回,函數瞬間結束,malloc開辟的空間就永遠無法釋放,從而導致內存的泄露
?? ?if (x == 1)
?? ??? ?return;
?? ?//.......
?? ?free(p);
?? ?p = NULL;
}
int main()
{
?? ?test();
?? ?return 0;
}

//情形二:

int* test()
{
?? ?int* p = (int*)malloc(40);
?? ?if (p == NULL)
?? ?{
?? ??? ?return p;
?? ?}
?? ?//.........
?? ?return p;
}
int main()
{
?? ?int* ptr = test();
?? ?//忘記釋放
?? ?return 0;
}

4、典型例題分析

4.1題目1:?

void GetMeory(char* p)//形參p,里面放的也是NULL
{p = (char*)malloc(100);//p被賦值,使用malloc申請100個字節的空間,此時p不再是NULL,而是所申請空間的首元素(是個地址)
}//這個函數一旦結束,因為p是形參,只能在函數內部使用,出了函數,p就銷毀了,但malloc申請的空間依然還在,從而發生空間泄露
void test(void)
{char* str = NULL;//str是局部變量GetMeory(str);//傳遞的是實參str,存放的是空指針strcpy(str, "hello world");//str此時依然為空指針,代碼必然崩潰,因為strcpy模擬實現包括解引用操作,而對NULL解引用會出現錯誤printf(str);
}
int main()
{test();return 0;
}

?改寫代碼:

void GetMeory(char** p)
{*p = (char*)malloc(100);//對p解引用得到的其實就是str
}
void test(void)
{char* str = NULL;GetMeory(&str);//這個時候str里面存放的就是動態內存開辟的100個字節的空間strcpy(str, "hello world");printf(str);//釋放free(str);str = NULL;
}
int main()
{test();return 0;
}

4.2題目2:

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
int main()
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);return 0;
}//這個邏輯上是沒有任何問題的,可以打印出hello,唯一的問題就是沒有free

4.3題目3:?

void test(void)
{char* str = (char*)malloc(100);//申請了100個字節的空間放到strstrcpy(str, "hello");free(str);//把str指向的空間釋放掉,但str并沒有變,動態開辟的空間實際上已經還給操作系統了if (str != NULL){strcpy(str, "world");//這個時候str就已經是一個野指針了,形成非法訪問printf(str);}
}
int main()
{test();return 0;
}

5、c/c++程序的內存開辟

?簡單了解即可!

6、柔性數組

typedef struct st_type
{
?? ?int i;
?? ?int a[0];//也可以寫成a[ ],柔性數組成員
}type_a;

6.1柔性數組的特點:

·?結構中的柔性數組成員前面必須至少一個其他成員;

·?sizeof返回的這種結構大小不包括柔性數組的內存;

·?包含柔性數組成員的結構用malloc( )函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。

6.2柔性數組的使用:

//柔性數組的使用,如何訪問空間
struct S
{int n;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S) + 40);//40是柔性數組想要開辟的字節大小,ps->n = 100;int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//柔性數組成員struct S* ptr=(struct S*)realloc(ps, sizeof(struct S) + 80);if(ptr!=NULL){ps=ptr;}//........free(ps);ps=NULL;//ptr已經賦給ps了所以不用釋放ptr,釋放平時即可return 0;
}
struct S
{int n;int* arr;
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){return 1;}ps->n = 100;ps->arr = (int*)malloc(40);if (ps->arr == NULL){return 1;}//使用int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//釋放free(ps->arr);ps->arr=NULL;free(ps);ps=NULL;return 0;
}

6.3柔性數組的優勢?

?第一個好處:方便內存釋放

如果我們的代碼是在一個給別人使用的函數中,你在里面做了二次內存分配,并把整個結構體返回用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體,但是用戶并不知道這個結構體內的成員也需要free,所以不能指望用戶來發現,所以,如果我們把結構體的內存以及成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存給釋放掉。

第二個好處:有利于訪問速度

連續的內存有利于提高訪問速度,也有益于減少內存碎片?

?

?

?

?

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

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

相關文章

二叉樹創建和遍歷

個人主頁 &#xff1a;敲上癮-CSDN博客二叉樹介紹&#xff1a;二叉樹(詳解)-CSDN博客 目錄 一、二叉樹的創建 二、二叉樹的遍歷 1.前序遍歷 2.中序遍歷 3.后序遍歷 4.層序遍歷 三、相關計算 1.總節點個數計算 2.葉子節點個數計算 3.深度計算 一、二叉樹的創建 關于…

如何在路由器上安裝代理服務:詳細教程

如何在路由器上安裝代理服務&#xff1a;詳細教程 步驟一&#xff1a;通過漏洞進入路由器系統開啟Telnet服務使用Telnet登錄路由器系統查看系統信息和CPU信息步驟二&#xff1a;交叉編譯MIPS程序 Go對MIPS的支持 安裝TFTP Server使用BusyBox tftp傳輸文件在路由器系統中下載編譯…

?機器學習正則化算法的總結。耗時10個小時完成。?

?純 干 貨~? 目錄 純干貨 1、L1 正則化&#xff08;Lasso 正則化&#xff09; 2、L2 正則化&#xff08;嶺正則化&#xff09; 3、彈性網絡正則化&#xff08;Elastic Net 正則化&#xff09; 4、Dropout 正則化&#xff08;用于神經網絡&#xff09; 5、貝葉斯Rid…

海外盲盒小程序:跨文化營銷的利器

在全球化的浪潮下&#xff0c;跨境電商正迎來前所未有的發展機遇。作為這一領域中的新興力量&#xff0c;海外盲盒小程序憑借其獨特的魅力和優勢&#xff0c;正逐漸嶄露頭角&#xff0c;成為跨文化營銷的利器。本文將探討海外盲盒小程序在跨文化營銷中的應用及其帶來的價值。 一…

【30天精通Prometheus:一站式監控實戰指南】第16天:snmp_exporter從入門到實戰:安裝、配置詳解與生產環境搭建指南,超詳細

親愛的讀者們&#x1f44b; ??歡迎加入【30天精通Prometheus】專欄&#xff01;&#x1f4da; 在這里&#xff0c;我們將探索Prometheus的強大功能&#xff0c;并將其應用于實際監控中。這個專欄都將為你提供寶貴的實戰經驗。&#x1f680; ??Prometheus是云原生和DevOps的…

【java11】java11新特性之增強String的API

Java11在String類上引入了一系列新的API增強&#xff0c;這些改進顯著提升了開發者在處理字符串時的便捷性和效率。 以下是Java11中增強String API的主要新特性&#xff1a; String.repeat()&#xff1a;重復給定次數的字符串。返回連接的字符串。String.isBlank()&#xff1…

ldap協議(常用于統一身份認證)與dict協議(在線詞典)

文章目錄 LDAPDICT LDAP LDAP&#xff08;Light Directory Access Portocol&#xff09;&#xff0c;輕量目錄訪問協議。 目錄是一個為查詢、瀏覽和搜索而優化的數據庫&#xff0c;它成樹狀結構組織數據&#xff0c;類似文件目錄一樣。 目錄數據庫和關系數據庫不同&#xff0c…

spring security 使用記錄

spring security 使用記錄 Bad credentials配置類密碼匹配 Bad credentials org.springframework.security.authentication.BadCredentialsException: Bad credentialsat org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticatio…

Docker安裝極簡版(三分鐘搞定)

什么是Docker? Docker是一個開源的應用容器引擎&#xff0c;它允許開發者打包他們的應用以及依賴包到一個可移植的容器中&#xff0c;然后發布到任何流行的Linux機器上&#xff0c;也可以實現虛擬化。容器是完全使用沙箱機制&#xff0c;相互之間不會有任何接口。 化。容器是…

日志脫敏功能

前言 數據安全尤為重要&#xff0c;最為簡單的防線就是防止重要信息&#xff08;身份證、手機號、姓名等&#xff09;明文顯示&#xff0c;對此需要在數據庫層、日志層等做好數據加解密。 思路 1、編寫需加密的正則模板、加密字段 2、重寫ch.qos.logback.classic.pattern.Me…

簡易圖像處理器的設計

1 概述 Python是一種高級、通用、解釋型的編程語言&#xff0c;由Guido van Rossum于1991年創造。它被設計為易讀易寫的語言&#xff0c;具有簡潔而清晰的語法&#xff0c;使得它成為許多領域的首選語言&#xff0c;如Web開發、科學計算、人工智能、數據分析等。結合本科階段以…

三維地圖校內導航系統解決方案

在如今的數字化時代&#xff0c;越來越多的學校開始實施智慧校園計劃&#xff0c;旨在為學生和教師提供更高效、便捷的學習和教學環境。智慧校園運用互聯網、大數據、人工智能等技術&#xff0c;對校園內各信息進行收集、整合、分析和應用&#xff0c;實現教學、管理、服務等多…

【matlab】繪圖插入并放大/縮小子圖

參考鏈接 代碼分為兩個&#xff1a;繪圖代碼與magnify.m 繪圖代碼就是普通的繪圖代碼&#xff0c;以下為例 %https://zhuanlan.zhihu.com/p/655767542 clc clear close all x 0:pi/100:2*pi; y1 sin(x); plot(x,y1,r-o); hold on y2sin(x)-0.05; y3sin(x)0.05; xlim([0 2*…

C#關鍵字概覽

C#是一種面向對象的編程語言&#xff0c;由微軟開發并作為.NET框架的一部分。它具有豐富的關鍵字&#xff0c;用于定義程序的結構和行為。本文將詳細介紹C#中的關鍵字&#xff0c;包括基本關鍵字、上下文關鍵字以及它們在C#編程中的使用方式。 訪問修飾符 訪問修飾符控制成員…

Python變量age:深入探索其內涵與運用

Python變量age&#xff1a;深入探索其內涵與運用 在Python的世界里&#xff0c;變量age不僅是一個簡單的標識符&#xff0c;它更是一個承載著豐富信息和功能的實體。今天&#xff0c;我們就來深入探索這個看似簡單的age變量&#xff0c;揭示其背后的奧秘和魅力。 四個方面&am…

供應SKYA21001思佳訊芯片現貨

長期供應各進口品牌芯片現貨&#xff1a; SKYA21001 QM11024TR13 QM12113TR13 QM42391 QM45392 QM28005 RF8020TR13 QM77033DTR13 QM56021TR13-5K 885171 QM77043 QM78207 QM77038TR13 SKY58081-11 QPF5752QTR13-5K RF7198TR13-5K SKY58255-11 SKY85720-11 …

Ubuntu中安裝和配置SSH的完全指南

目錄 前言 第1步&#xff1a;安裝SSH服務器 第2步&#xff1a;檢查防火墻設置 第3步&#xff1a;連接到SSH服務器 第4步&#xff1a;配置SSH服務器&#xff08;可選&#xff09; 更改SSH端口 禁用root登錄 第5步&#xff1a;公鑰認證&#xff08;建議&#xff09; 結論…

XSS Challenges 闖關游戲環境準備:深入指南

在網絡安全領域&#xff0c;理解并掌握跨站腳本攻擊&#xff08;XSS&#xff09;的防御技巧至關重要。為了幫助學習者深入實踐XSS攻擊與防御&#xff0c;“XSS Challenges” 闖關游戲提供了一個實操平臺。本文將詳細介紹如何準備這一環境。 1. 環境準備概述 XSS Challenges 闖…

Kubernetes 之 Secret

Kubernetes 之 Secret Secret 的定義 Secret 解決了密碼、token、秘鑰等敏感數據的配置問題&#xff0c;它避免了把這些敏感數據直接暴露在鏡像或者 Pod 的配置文件中。但是它只是一種相對安全的策略&#xff0c;我們還是可以在容器內找到這些信息。 Secret 的認證方式 認證…

eclipse-向Console控制臺輸出信息

首先這里主要用到的是org.eclipse.ui.console這個包&#xff0c;所以現在順道先來了解一下&#xff1a; org.eclipse.ui.console是一個可擴展的console視圖插件&#xff0c;利用它可以實現各種console&#xff0c;并把它們顯示出來。該插件本身就實現了一個Message Console&…