动态内存开辟

🐻动态内存开辟🐻

东风夜放花千树,更吹落,星如雨。

————《青玉案.元夕》

《更吹落,星如雨》|插画|插画习作|狐狸狐狸鱼 - 原创作品 - 站酷 (ZCOOL)

✨前言

当所需空间是变化或者是不确定的时候,开辟数组这类的开辟明确固定内存空间的方法。总归是一定的限制。故我们就要对内存的动态需求有一定的响应和相应,所以接下来了解一下专门应对这种情况的机制->动态内存分配机制。

一、🫥动态内存开辟的相关函数

1.🤖mallocrealloc的使用

在这里插入图片描述

函数作用分析:在对空间上开辟一块size字节大小的内存块,并返回此内存块的指针。

参数:size是要开辟的内存块所占的字节大小。

返回值:返回空指针,并进行强制转换。

注意:在使用这个函数的时候要进行强制类型转换,并用所开辟类型的指针接收。

使用基本规则: (需要开辟的数据类型指针)malloc(sizeof(期望数据类型) * 想要连续内存块所包含的)

同时在使用的时候值得注意的是,要了解这个函数调用返回规则。

  • 正常调用时,开辟成功会返回一个指向内存块的指针
  • 调用失败的时候,异或是空间不足则会返回NULL指针。
  • 如果是传入的是0,仍然会开辟,并返回有效指针

所以在调用完这个函数后要进行判空行为:

int * p = (int*)malloc(sizeof(int) * 10);
if(!p)
{
    perror("malloc");//这个函数得传入自定义信息当程序出现错误的时候将会将错误码解析后的结果放在自定义信息后边
    return 1;
}

在这里插入图片描述

函数分析:是针对动态开辟得到的内存进行释放。归还给操作系统

参数分析:传入的应该是在堆上开辟的对应内存块的指针。

注意这个函数是跟malloc是成对出现的,一旦有malloc就一定要出现free来对空间进行释放。,但是对应动态开辟的指针不会变空,要进行手动置空,以防出现野指针。

所以就有以下范式:

int  p = (int*)malloc(sizeof(int)*3);
free(p);
p = NULL;

了解了这些,之后我们心中就会不禁生出疑惑为什么变量之类的就不需要我们释放而这种动态开辟的内存,需要我们主动进行销毁呢?

在解释这个问题之前我们要对内存空间要有更加深刻的了解。

在这里插入图片描述

可见释放内存是一个优秀程序员在动态开辟后的必要操作。

换个角度来看,请设想这样一个场景,你是一个不释放内存的屑程序员,每天开辟一点每天开辟一点,公司的服务器内存就被占用一点,本来在你自己写程序的时候,程序结束了,就会将这段内存处理掉。但是在公司里,服务器可是一天都不敢关,每次到你负责的接口就泄露一点内存,到将来的某一天服务器崩了,一查是你的原因,那你觉得leader会怎么处理你呢。

你可能只能删库跑路了🥲:

程序员“删库跑路”,一己之力蒸发公司市值超10亿,300万商铺遭瘫痪 - 知乎

当然作为一个懂得是会主义核心价值观的新时代青年肯定是不能干出来这种事的吧🐼!?嗯,肯定的。😏

编程的第一法则:如果您的代码以某种莫名方式跑起来了,就不要再碰它了-表情包就找i表情

花开两朵,咱们各表一枝。回归正题,了解完了一些内存的基本区域储存规则,趁热打铁我们再来认识几个函数

2.realloccalloc

在这里插入图片描述

函数作用机制:

  • 如果相对于原来内存块有所减少那么,那么那些多出的部分就会进行释放。

  • 如果在增加时此块内存块后边有足够的空白内存,就会增加开辟,并将原来的地址进行返回

  • 若增加的时候此处内存块的后面没有足够的空白内存的话,这个函数则会找一个足够的部分将重设后的内存开辟在这个地方。并返回新开辟的内存空间的地址。

  • 因为返回的是空类型指针,所以要进行对应的强制类型转换,和接收对应。

  • 值得注意的是,指针传入的参数也可以是空指针,如果传入的是NULL的话,那就会在堆内存块任意找一个空内存,进行对应的开辟,作用相当于malloc。

在这里插入图片描述

应用示例:

//前文已经开辟了四十字节的动态空间p,下面是对其进行调整
//由之前的函数调用机制所以要用临时变量进行接收,同时要进行判空
int * ret = (int *)realloc(sizeof(int )*50);
if(ret)
{
    p = ret;//不为空所以可以进行赋值
}
else
{
    perror("realloc");//对错误信息进行打印
    return 1;
}

在这里插入图片描述

函数作用分析:

在堆内存上动态开辟一个数组内存块并将数值初始化成0,然后对堆内存的地址进行返回。

参数分析:

num

所要开辟的元素个数

size

元素所需要占据的字节大小。

返回值:

因为返回值是空类型的指针,所以要经常进行指针类型的强制类型转换,因为,开辟失败会返回空类型的指针,所以在使用式用临时变量进行接收,而且进行判空,才能使用

使用范例:

int * p =  (int * )calloc(5,sizeof(int));

在这里插入图片描述

二、调用时值得注意的误区

1.对空指针的误解引用

void test()
{
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;
    free(p);
}

乍一看可能没什么问题,但是考虑一下malloc的特点就可以明白,他会有两种情况:若开辟失败会返回空指针,那么下面的解引用操作就会对空指针进行操作,但是这种操作在编译器看来是非法的,因为这个空地址并无意义,没有指向,所以会报错。导致程序出错

2.动态空间的越界访问

请看下面的代码:

void test()
{
    int n =0;
    int *p = (int *)malloc(sizeof(int) * n);//四个整形大小边界
    int i= 0for(i = 0;i <= n;i++)
    {
        p++;//因为p是起始位置的大小所以只能进行n - 1自增,但是当i= n是就进行了n次自增,会对为内存块的未使用内存非法访问。
	}
}

3.对非动态开辟内存进行释放

void test()
{
    int a = 0;
    free(a);//这个free  ok?
}

当然,众所周知的是free是通过堆内存地址对堆内存上的内存进行回收。对栈区的空间无法进行干涉,所以不能对非动态开辟的内存进行释放。

4. 只是对动态开辟的内存进行部分释放

int * p = (int *)malloc(sizeof(int) * 3);
p++;//使得原来指向起始位置的指针产生影响,使得指向下一个位置
free(p);//只是对动态内存进行部分释放,因为已经释放p指针已经变成了野指针了,不可访问所以未释放部分就会内存泄漏

5.对一块动态内存空间多次进行释放

void test()
{
    int * p = (int * )malloc(sizeof(int) * 3);
    free(p);//第一次释放对传入指针的内存空间进行变化使得内存空间释放,也就让p的地址变成了非法访问空间了
    free(p);//传入的是非法地址,无法对非法空间进行访问以及改变,所以报错。
}

6.忘记释放动态内存空间

void test()
{
    int * p = (int *)malloc(sizeof(int) * 3);
    //进行判空
    if(p)
    {
        *p  =10;
	}
    
}//因为函数调用结束了栈帧中的临时变量就会销毁以至于没有指针记录空间在堆区上的位置无法回收
//这个导致的后果是内存泄露,是一个十分严重的后果
int main()
{
    test();
    return 0;
}

内存泄漏:即内存因为未知原因消失,了其中对堆区内存的不释放就是一个主要原因,因为不对这个空间进行释放就会一直占用着这块内存使得你再申请空间的时候,编译器对这块空间就会忽视,使得能用的空间变得越来越少,不但占用,最后致使崩溃。

一定要注意:内存一定要适时释放。

三、一些程序谬误笔试题以及解析

1.

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

请问调用上边的函数会有什么结果?

分析:

我们先从test函数主体开始读,开始理解首先前两句的意图是创建一个预期指针用GetMemory来创建内存空间并且存放,所以接下来我们放眼到局部,GetMemory内部定义可以看到参数是字符指针,不要一看是指针类型参数就当成实参,因为实参和形参只是相对而言的,针对同一个变量才有意义,但是,在这里可以看见,这个函数的意图是为了对传入指针的内容进行更改,但是p 只是Test函数中str指针的一份临时拷贝,并不能更改其中内容,所以出了GetMemory,p就会销毁,更改的是实参 开辟的内存地址就也不会被str记住。所以str还是空指针,输出只能是空。

2.

char *GetMemory(void)
{
    char p[] = "hello world";//在栈帧中创建临时数组,一旦出了函数就会销毁此块栈帧空间,地址指向空间就会进行释放
    return p;//返回也无法正确访问这块内存
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);//打印结果   错误
}

请问调用上边的函数会有什么结果?

分析:

这道题虽然与上道题不尽相似,但是上文是利用动态开辟的堆区空间,而这个只是在栈桢空间创建的临时数组空间,并不能通过记住地址在任何地方将进行访问。出了函数定义空间就无法发挥作用,如果访问的话则是野指针。所以Test函数中的str得到的是无效内存空间的地址。编译器无法通过。

3.

Void GetMemory2(char **p, int num)//因为传的是二级指针所以其中的解引用更改会对相应的指针进行修改。
{
	*p = (char *)malloc(num);//动态开辟内存块
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");//会将整个字符串复制到动态开辟的空间连带着‘\0'
    printf(str);//所以输出时就可以直接输出到'\0'
    //打印hello
}

4.

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

本题考查点即动态开辟后指针的状况,要了解到free的基本调用机制,并不会将对应的动态指针置空,只会将指向的那个内存块给释放掉,所以,他的本来意图的判空就是为了检测指针。只不过如果不进行置空的话,就会出现野指针的情况。,更改意见:在free后边,进行置空即可。

后记

键盘敲烂年薪三十万,兄弟们必拿下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EZ51hWI9-1674578250914)(https://ts1.cn.mm.bing.net/th/id/R-C.71bcf3367dc08eaea64c037d78f11735?rik=EeIfy23SjM8J1g&riu=http%3a%2f%2fimg95.699pic.com%2fphoto%2f40006%2f9556.jpg_wh860.jpg&ehk=DrTqjNsLr6IwbGAWf7aCmJn%2fMXRlovAYPs1SMUyGHbg%3d&risl=&pid=ImgRaw&r=0)]
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}


> 本题考查点即动态开辟后指针的状况,要了解到free的基本调用机制,并不会将对应的动态指针置空,只会将指向的那个内存块给释放掉,所以,他的本来意图的判空就是为了检测指针。只不过如果不进行置空的话,就会出现野指针的情况。,更改意见:在free后边,进行置空即可。

# 后记

> 键盘敲烂年薪三十万,兄弟们必拿下
>
> ![在这里插入图片描述](https://img-blog.csdnimg.cn/ffb04701cd704625b159bacb8e187cc4.png#pic_center)

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难扰浮生梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值