網(wǎng)站首頁 編程語言 正文
前言
我們已經(jīng)掌握的內(nèi)存開辟的方法有兩種
int a = 10;? ? ? //在棧空間上開辟4個字節(jié)的空間
int a[10] = {0};? //在棧空間上開辟40個字節(jié)的連續(xù)空間
這些開辟方式都有兩個共同的特點:
1.空間開辟大小是固定的
2.數(shù)組在申明的時候,必須指定數(shù)組的長度,它需要的內(nèi)存在編譯的時候分配
我們?yōu)槭裁匆獙崿F(xiàn)動態(tài)管理內(nèi)存呢,這又什么作用呢?
我們對于空間的需求不僅僅只是上面兩種,有時候我們到底需要多少空間,需要運行之后才能知道,這個時候就需要動態(tài)開辟內(nèi)存空間,即動態(tài)內(nèi)存函數(shù)就誕生了!
動態(tài)內(nèi)存函數(shù)有那些?
1.malloc和free
2.calloc
3.realloc
malloc和free
malloc是C語言提供的一個動態(tài)內(nèi)存開辟的函數(shù):
?這個函數(shù)向內(nèi)存申請一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
1.如果開辟成功,則返回一個指向開辟好空間的指針。
2.如果開辟失敗,則返回一個 NULL 指針,因此 malloc 的返回值一定要做檢查。
3.返回值的類型是 void* ,所以 malloc 函數(shù)并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
4.如果參數(shù) size 為 0 , malloc 的行為是標準是未定義的,取決于編譯器。
void*的返回類型,使用的時候根據(jù)情況強制類型轉(zhuǎn)換
C語言還提供free函數(shù),專門是用于做動態(tài)內(nèi)存的釋放和回收的,函數(shù)原型如下:
free函數(shù)是用來釋放動態(tài)開辟的內(nèi)存:
1.如果參數(shù) ptr 指向的空間不是動態(tài)開辟的,那free函數(shù)的行為是未定義的。(會報錯)
2.如果參數(shù) ptr 是NULL指針,則函數(shù)什么事都不做。
圖文演示:
頭文件要加上 malloc.h?
?代碼演示:
int main()
{
int num = 0;
scanf("%d", &num);
//int arr[num] = { 0 }; num 在 [] 中
//VS 不支持這樣,但是可以使用動態(tài)內(nèi)存函數(shù),實現(xiàn)動態(tài)數(shù)組
int* ptr = (int*)malloc(sizeof(int) * num);
if (NULL == ptr) {//進行判斷是否創(chuàng)建成功
perror("malloc::ptr");
}
else {
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr); //使用free函數(shù)釋放動態(tài)申請的ptr
ptr = NULL; //將ptr free之后,置為NULL,防止野指針非法訪問
}
return 0;
}
而且malloc函數(shù)創(chuàng)建的空間不會進行初始化,里面存放的是隨機值,如圖
1.2 calloc
calloc函數(shù)也是C語言提供的,用來動態(tài)內(nèi)存分配,原型如下:
calloc函數(shù)介紹:
1.函數(shù)的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個字節(jié)初始化為 0。
2.與函數(shù) malloc 的區(qū)別只在于 calloc 會在返回地址之前把申請的空間的每個字節(jié)初始化為全 0。
實操圖文分析:
?代碼演示:
int main()
{
int num = 0;
int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函數(shù)
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr);//free 動態(tài)申請的ptr
ptr = NULL;//置為NULL,防止野指針越界訪問
return 0;
}
對于calloc動態(tài)申請的空間是否每一個字節(jié)都變?yōu)?呢?我們來看下圖
這也是calloc和malloc函數(shù)的最大的區(qū)別,是否自動初始化,前者有,后者無
realloc
realloc也是C語言提供的動態(tài)內(nèi)存申請函數(shù),使得動態(tài)內(nèi)存管理更加靈活。
本質(zhì)是可以對已經(jīng)動態(tài)申請過的空間進行增容,是更加靈活的。
有時會我們發(fā)現(xiàn)過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時 候內(nèi)存,我們一定會對內(nèi)存的大小做靈活的調(diào)整。那 realloc 函數(shù)就可以做到對動態(tài)開辟內(nèi)存大小 的調(diào)整。
?函數(shù)原型如下,并對兩個形參ptr和size進行分析:
如上圖:
1.ptr可以為NULL,相當于malloc一個新的空間,ptr是要調(diào)整的內(nèi)存地址
2.size同樣可以為0,則返回值取決于特定的庫實現(xiàn):它可能是空指針,也可能是不應(yīng)取消引用的其他位置。size是調(diào)整之后的大小
3.返回值為調(diào)整之后的內(nèi)存起始位置。
4.這個函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會將原來的數(shù)據(jù)移動到新空間。
realloc調(diào)整內(nèi)存空間的時候有兩種情況:
第一種情況:當原有空間之后的內(nèi)存空間足夠的時候
第二種情況:當原有空間之后的內(nèi)存空間不夠時
如圖所示:
?因為這兩種情況是隨機發(fā)生的,不能控制必須使用哪一種,所以我們就要小心一個事情,不要用原來動態(tài)開辟的變量ptr來直接接收realloc,應(yīng)該創(chuàng)建臨時變量接收,先判空,之后再賦值給ptr
代碼圖示:
?可以自行測試:
int main()
{
int* p = (int*)malloc(sizeof(int)*10);
if (p == NULL) {
perror("malloc::p");
}
else {
printf("%p\n", p);
}
int* ptr = (int*)realloc(p, sizeof(int) * 20);//創(chuàng)建臨時變量
//如果使用 int* p = (int*)realloc(p,....這樣的話如果創(chuàng)建失敗,返回NULL,
//這樣的話p的內(nèi)容就沒有了,所以創(chuàng)建臨時變量ptr,然后下面判空之后可以交換
if (NULL == ptr) {
perror("realloc::ptr");
}
else {
p = ptr;
ptr = NULL;
printf("%p\n", p);
}
free(p);
p = NULL;
return 0;
}
常見動態(tài)內(nèi)存錯誤(案例分析)
對于NULL指針的解引用操作
意思就是要學(xué)會使用動態(tài)內(nèi)存函數(shù)的時候嗎,要進行判空,不然誰知道有沒有問題NULL
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
*p = 10;//這個時候誰知道p是不是NULL,如果是NULL,那么這就是非法訪問,是錯誤
free(p);
return 0;
}
對動態(tài)開辟空間的越界訪問
就是說,開辟多少空間就是多少空間,不能越過這個字節(jié)數(shù)的界限訪問空間外的地址
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (NULL == p) {
perror("malloc::p");
}
else {
for (int i = 0; i < 100; i++) {
*(p + i) = i + 1;//當i等于10的時候就開始越界訪問
}
for (int i = 0; i < 11; i++) {
printf("%d ", *(p + i));
}
free(p);
p = NULL;
}
return 0;
}
和數(shù)組一樣,不要越界,不需要多想什么額外的東西
對非動態(tài)開辟內(nèi)存使用free釋放
free可以放置NULL進去,不會報錯,但是不能放非動態(tài)開辟的內(nèi)存,會報錯
圖示分析free函數(shù):
?代碼演示:
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
int a = 10;
free(&a);//非動態(tài)內(nèi)存開辟的,會報錯
//free(NULL); //沒有什么反應(yīng),程序正常
return 0;
}
使用free釋放了動態(tài)開辟內(nèi)存的一部分
就是說如果動態(tài)開辟內(nèi)存之后的p指針的位置發(fā)生改變的話再去釋放free(p)只是釋放一部分
代碼演示:
//舉例
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
p++;
free(p);//這個的時候p向右移動一個整型字節(jié)空間,再進行釋放,那么先前那個空間就沒被釋放
return 0;
}
對同一塊動態(tài)內(nèi)存進行多次釋放
多次釋放會報錯的
圖示:
動態(tài)開辟空間忘記釋放(內(nèi)存泄漏)
所以我們要養(yǎng)成當一個動態(tài)空間不用的時候就free他,放置內(nèi)存泄露
代碼演示:
int main()
{
//test();
while (1) {
malloc(1);//一直申請就是不釋放
}
}
練習(xí)題
第一個
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
//改為傳遞地址就可以或者就是用str接收
//str= GetMemory(str);//實際上用臨時變量接收更好
strcpy(str, "hello world");
printf(str);
//用完釋放
//free(str);
//str=NULL;
}
1.傳值操作,就算p申請了空間也不會使得str發(fā)生改變,所以str依舊是NULL,不能有strcpy
2.內(nèi)存泄漏,?GetMemory(str);未釋放p的空間?
第二個
char *GetMemory(void)
{
//修改為:
//static char p[] = "hello world";
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
典型的返回棧地址問題,p數(shù)組是局部變量 ,確實是返回了p的地址給str,但是GetMemory函數(shù)結(jié)束之后,數(shù)組p的空間就沒有,再訪問p的地址(printf(str))就會非法訪問
第三個
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(str);
//str=NULL;
}
沒有釋放str動態(tài)開辟的空間,沒有free(str),str=NULL
第四個
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(str);
//str=NULL;
}
在使用str之前就釋放了str申請的空間,釋放之后str!=NULL,保留原來地址,str這個時候已經(jīng)是野指針了(因為沒有了對相應(yīng)空間的訪問權(quán)限),之后確實是輸出了world,但是從if語句就已經(jīng)錯誤了,置為str=NULL 就可以了
總結(jié)
本文主要是對于malloc、calloc、realloc、free函數(shù)的介紹和使用細節(jié)的說明,還有一些關(guān)于動態(tài)內(nèi)存管理的函數(shù),學(xué)會了這些,對于以后數(shù)據(jù)結(jié)構(gòu)的內(nèi)容會更加得心應(yīng)手,所以希望大家能多多支持,接下來,下一章,我們跟大家講解一下,文件管理的內(nèi)容。學(xué)會了就可以更新通訊錄啦!!!
原文鏈接:https://blog.csdn.net/qq_63319459/article/details/128695300
相關(guān)推薦
- 2022-04-18 Python的類成員變量默認初始值的坑及解決_python
- 2022-06-09 ASP.NET?Core基于現(xiàn)有數(shù)據(jù)庫創(chuàng)建EF模型_實用技巧
- 2022-06-02 Docker部署項目完全使用指南(小結(jié))_docker
- 2021-12-16 Docker快速部署SpringBoot項目介紹_docker
- 2022-07-13 【arthas】使用arthas定位接口耗時問題、無日志情況下排查問題
- 2022-10-10 pandas?修改列名的實現(xiàn)示例_python
- 2022-12-15 conda創(chuàng)建環(huán)境過程出現(xiàn)"Solving?environment:?failed"報錯的詳細解決方
- 2022-09-25 navicat連接遠程服務(wù)器報錯代碼:10038
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支