網站首頁 編程語言 正文
前言
有時候我們需要的空間大小不確定,需要隨著程序需要的空間而變化, 那以數組開辟的固定大小的空間就不適用了, 這時候我們就需要動態分配開辟空間了。當空間不夠時就擴容。動態開辟是在堆區開辟一塊連續可用空間,并返回這塊空間的地址。有三種函數malloc, calloc和realloc。我們動態內存分配就在堆區開辟空間
上面的四個區只有堆區的空間是需要手動釋放的
free函數
free函數是專門用來對動態開辟內存的回收和釋放的。當我們不需要再使用動態開辟的空間時,一定要free釋放空間,因為是在堆上開辟的空間,所以不會隨著出了作用域而銷毀,需要我們free釋放,避免內存泄漏,并置空(將指針置為NULL),避免形成野指針。
內存釋放是標記刪除, 只會修改當前空間的所屬狀態,并不會清除空間內容。
當然內存泄漏也不是都有危害,但為了養成良好的代碼習慣,動態開辟后一定要free
void free (void* ptr);
1.如果參數ptr指向的內存空間不是動態內存開辟的,那free函數的行為是未定義的,就會報錯,所以free函數是針對動態開辟的空間
2.如果ptr是 NULL空指針,那么free函數什么都不做。
下面我們來實現一下。
動態內存分配需要調用頭文件#include
malloc函數
void* malloc (size_t size);
malloc函數用來動態開辟的, size是開辟所需空間的大小,單位:字節。
并返回起始地址。返回類型是void*,表示我們可以開辟任意類型的空間,同時我們需要強制類型轉換成我們開辟空間類型的指針,再用指針接收
例如我們開辟的是int型的空間,就需要先轉換為(int*),再用int*的指針接收
開辟float型的空間,就需要先轉換為(float*),用于float*的指針接收
int* p = (int*)malloc(sizeof(int) * 10);
重點:
1.malloc函數在開辟空間后需要判斷開辟空間是否成功,若開辟成功會返回開辟好的空間的指針,開辟失敗會返回空指針。
2.malloc函數并不會對開辟空間進行初始化,空間內容為隨機數。
下面是關于malloc函數的例子,主函數如下:
主函數中第一段代碼就是在堆上動態開辟10個int型大小的空間,通過int*型的指針p去維護這塊空間,如果空間開辟失敗就會返回NULL,所以我們需要判斷是否開辟成功。
#include#include #include #include int main() { int* p = (int*)malloc(sizeof(int) * 10); if (p == NULL) { //我們也可以直接eixt(-1)異常退出,或是return; printf("%s\n", strerror(errno)); //strerror(errno)是用來判斷開辟失敗的錯誤原因 return -1; //error是錯誤碼 } //需要調用最下面的兩個頭文件 for (int i = 0; i < 10; i++) { printf("%d ", p[i]); } printf("\n"); return 0; }
判斷p為NULL時,也可以直接打印開辟失敗,不需要調用strerror函數,也不用包含那兩個頭文件
if (p == NULL) { printf("malloc failed\n"); exit(-1); }
calloc函數
void* calloc (size_t num, size_t size);
calloc函數與malloc類似 ,calloc可以看作malloc+memset
?參數num是開辟空間的元素個數,參數size是元素的大小,單位:字節。
函數的功能是為 num 個 size大小 的元素開辟一塊空間,并且把空間的每個字節初始化為0。
calloc函數與malloc函數不同點在于:會將開辟的空間都初始化為0(按字節初始化為0)。
#include#include #include #include int main() { int* p = (int*)calloc(10, sizeof(int)); if (p == NULL) { printf("%s\n", strerror(errno)); return -1; } for (int i = 0; i < 10; i++) { printf("%d ", p[i]); } printf("\n"); return 0; }
下面看運行結果都為0
realloc函數
realloc函數讓動態內存管理變得更加靈活,空間不夠時可以對動態開辟的空間擴容
void* realloc(void* ptr, size_t szie);
參數ptr為需要擴充動態內存分配的空間的地址
size是 調整之后新大小,返回參數為調整之后的內存起始位置。
realloc在調整內存空間的是存在兩種情況:
1.原地擴容
在需要擴容的空間后有足夠的空間進行擴容,要擴展內存就直接原有內存之后直接追加空間,原來空間的數據不發生變化。
2.異地擴容
原有空間之后沒有足夠多的空間時,擴展的方法是:在堆空間上另找一個合適大小 的連續空間來使用。這樣函數返回的是一個新的內存地址。
pc指向的空間的數據會遷移到ptr指向的空間,此時不需要將pc的空間free,系統會自動釋放。
下圖通過兩行代碼了解什么是異地擴容,可以看到p1和p2的地址發生了變化
p1的值是0x006b56b8? p2的值是0x006bf4d0,所以擴容后的空間地址與原空間地址不同,這就是異地擴容
當然擴容失敗就會返回NULL,異常退出了
下面是malloc和realloc的聯合使用,我們擴容后要將擴容后的空間再次交給原指針去維護
所以將p=ptr
#include#include int main() { int* p =(int*) malloc(sizeof(int)*10); if (p == NULL) { printf("malloc faied\n"); return -1; } int* ptr = (int*)realloc(p, sizeof(int) * 20); if (ptr == NULL) return -1; p = ptr; for (int j = 0; j < 20; j++) { printf("%d ", p[j]); } printf("\n"); return 0; }
運行結果可以看到都是隨機數,所以realloc函數也不會初始化
可以看出realloc與malloc相似,都不會初始化。同時這也是realloc和malloc的一個特性,
當要擴容的對象為空時,realloc可以當作malloc函數使用。
擴充
我們一般動態開辟的空間都是結構體,下面簡單介紹一下開辟結構體類型的空間
我們首先定義了一個結構體類型,里面定義的是int型的數據data,還有一個next型的指針,存放下一節點的地址,這就是數據結構的單鏈表結構,不了解的小伙伴可以簡單看一下
pc指向開辟的一個節點用,p指向開辟的另一個節點用,將pc指向的結構體中的next保存p指向節點的地址
#include#include typedef struct QueueNode { DataType data; struct QueueNode* next; }QueueNode; int main() { QueueNode* pc = (QueueNode*)malloc(sizeof(QueueNode)); QueueNode* p = (QueueNode*)malloc(sizeof(QueueNode)); if (pc == NULL && pc == NULL) { printf("malloc failde\n"); exit(-1); } pc->data = 10; pc->next = p; p->data = 20; p->next = NULL; printf("%d %d\n", pc->data, p->data); printf("%p %p %p\n", pc->next, p, p->next); free(pc); free(p); pc = p = NULL; return 0; }
重點:動態開辟使用完后,一定要記得free,置空。
了解完上面的內容,是不是對動態分配有了更深的理解。
malloc/calloc/realloc區別總結
相同點:
1.都是從堆上申請空間
2.都需要對返回值判空
3.都需要用戶free釋放
4.返回值類型相同(void*)
5.都需要類型轉化
6.底層實現上是一樣的,都需要開辟多余的空間,用來維護申請的空間
可以輸入以下代碼觀測內存:
#include#include int main(){ int *p= (int *)malloc(sizeof(int )*10); return 0; }
不同點:
1.函數名字不同和參數類型不同。
2.calloc會對申請空間初始化,并且初始化為0,而其他兩個不會。
3.malloc申請的空間必須使用memset初始化
4.realloc是對已經存在的空間進行調整,當第一個參數傳入NULL的時候和malloc一樣
總結
原文鏈接:https://blog.csdn.net/weixin_46016019/article/details/121658478
- 上一篇:C語言數據結構之二叉樹詳解_C 語言
- 下一篇:C語言數據結構之堆排序詳解_C 語言
相關推薦
- 2022-07-16 Linux中啟動Docker容器報錯:Error response from daemon: dri
- 2022-04-23 C語言復雜鏈表的復制實例詳解_C 語言
- 2022-08-26 一篇文章學會GO語言中的變量_Golang
- 2022-07-15 go?GCM?gin中間件的加密解密文件流處理_Golang
- 2022-08-23 Python中應用Winsorize縮尾處理的操作經驗_python
- 2022-04-20 Django學習之路之請求與響應_python
- 2022-12-21 QT+Quick實現自定義組件的示例詳解_C 語言
- 2022-12-14 Go語言defer的一些神奇規則示例詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支