【C语言】零基础教程——动态内存管理

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 【C语言】零基础教程——动态内存管理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

目录

1.为什么存在动态内存分配

        1.1我们熟知的开辟内存方式有

         1.2已知的开辟方式有以下两个特点

        1.3原因

 2. 动态内存函数的介绍

        2.1 malloc

        malloc函数的特点

        malloc函数具体使用

                那如果malloc函数开辟失败会怎么样呢?

                在C语言中有这么一个定义好的值:INT_MAX

        什么是栈区,堆区?

2.2 free

        先来看看free函数的特点吧~

        具体应用&&带入刚刚的例子

        和free前有什么一样的,有什么不一样的?

2.2 calloc

        calloc函数有什么特点呢?和malloc函数有什么区别呢?

2.3 realloc

        realloc函数的特点 

        例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

        举个例子:

3. 常见的动态内存错误

        对NULL指针的解引用操作

        对动态开辟空间的越界访问

        对非动态开辟内存使用free释放

        使用free释放一块动态开辟内存的一部分

        对同一块动态内存多次释放

 动态开辟内存忘记释放(内存泄漏)



1.为什么存在动态内存分配

1.1我们熟知的开辟内存方式有

        在栈区上开辟4个字节的空间

int i = 0

        在栈区上开辟16个字节的连续空间

int i[4] = { 0 };

 1.2已知的开辟方式有以下两个特点

1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

1.3原因

        但是,有些时候,我们要通过运行程序才能知道数组需要的空间大小,亦或是在程序运行期间,需要增大数组的空间,这又该如何去做呢?

【C语言】零基础教程——动态内存管理

         这就不得不引入动态内存开辟的概念了~


 2. 动态内存函数的介绍

2.1 malloc

        C语言提供了来自stdlib.h头文件中动态内存开辟的函数

void* malloc (size_t size);

此函数会向 堆区 申请一段连续可用空间,开辟成功则返回开辟空间的起始位置的地址 

malloc函数的特点

        开辟成功,返回这块空间的指针;

        开辟失败,返回一个NULL指针,所以每一次开辟空间一定要检查;

        返回类型为void* ,所以需要我们根据自己的需求,将返回的指针强制类型转化成我们需要的指针;

        若size的单位是字节,若参数为0,malloc是未定义的,取决于编译器;

malloc函数具体使用

举个例子:开辟10个整形的空间,并分别赋值0~9的数字

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回1表示不正常
	}
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
	return 0;
}

        这时候可能有同学可能会问了:

        那如果malloc函数开辟失败会怎么样呢?

演示一波~

        在C语言中有这么一个定义好的值:INT_MAX

【C语言】零基础教程——动态内存管理

 这是什么东西呢?

        叫整形最大值,有多大呢?

转到定义来看看:

【C语言】零基础教程——动态内存管理

 不得了,是一个21亿多的一个数字,这时候,让malloc函数开辟这么打一个空间试试?

【C语言】零基础教程——动态内存管理

嗯…好吧没想到电脑内存还蛮大的,开辟出来了…

【C语言】零基础教程——动态内存管理

 这样,咱来个INT_MAX*INT_MAX

走~

【C语言】零基础教程——动态内存管理

 还没运行,就已经提醒你了哈哈哈。

一个开辟空间失败的错误示范;

还有一个点:

什么是栈区,堆区?

        内存里面分为三个区,就是栈区,堆区,静态区(实际上还有很多其他区,主要讲这几点),栈区是用来存放临时变量的,变量空间定义好就不能再变了,而malloc、calloc、realloc这几个函数就会在堆区上开辟空间,定义好后根据需求,可以继续改变变量所占空间的大小,静态区用来存放全局变量;(如下图)

【C语言】零基础教程——动态内存管理

细致一点就如下图:

【C语言】零基础教程——动态内存管理

 

        有没有思考过这样一个问题?

       即使一个空间用完了,退出程序,系统会自动回收空间, 但如果一个空间用完了,不想再用了,程序还在需要继续运行,继续开辟空间,那岂不是会造成内存泄露?

2.2 free

        不用担心~😶‍🌫️

        C语言专门提供了一个函数free,用来解决内存泄漏,回收空间的功能!原型如下:

void free (void* ptr);

先来看看free函数的特点吧~

1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。

3.来源于stdlib.h

具体应用&&带入刚刚的例子

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回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;
}

这里又是一个细节~

p = NULL?

【C语言】零基础教程——动态内存管理

 想象以下,释放了内存,可以不管这个指针p了吗?

        经过调试可以看到:

free前:

【C语言】零基础教程——动态内存管理

 注        意:p,10可以展示出10个元素,若只是p,只会显示一个值;

free后:

【C语言】零基础教程——动态内存管理

和free前有什么一样的,有什么不一样的?

        free后,内存还给操作系统,动态开辟的空间里赋的值全部变为随机数,但是,p指针指向的地址依旧没变(x64环境)。

        这样会使得指针p十分危险(野指针),即使空间还给了操作系统,但是p地址依旧不变,导致如果以后不小心误用了指针p会导致内存的非法访问!

所以一定要注意,每次释放完动态内存开辟的空间后,请将指针置为NULL;

2.2 calloc

        C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配

void* calloc (size_t num, size_t size);

calloc函数有什么特点呢?和malloc函数有什么区别呢?

calloc函数的特点是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0;

区别在于: malloc会将创建好的空间赋上随机值,而calloc 会在返回地址之前把申请的空间的每个字节初始化为全0;

举个例子:

【C语言】零基础教程——动态内存管理

 可以从内存看出calloc函数开辟好空间,内存就已经全部初始化好成全零了

calloc貌似就等于malloc+memset(初始化全零)

【C语言】零基础教程——动态内存管理

所以如果在需要对开辟好的空间初始化成全零,calloc无疑是个很好的选择!

2.3 realloc

        此函数的出现让动态内存开辟更加灵活!实现了真正意义上的 “动态”~

🤔写代码时,我们常常会遇到这样一个场景,开辟了一个40个字节的空间,但是后来实际操作中,又感觉不够用了,或者是感觉开辟大了,怎么办呢?

        那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型:

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

realloc函数的特点 

1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

具体来讲realloc在调整内存空间的是存在两种情况:(如下图解)

例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

情况一:

【C语言】零基础教程——动态内存管理

 情况二:

【C语言】零基础教程——动态内存管理

由于上述的两种情况,realloc函数的使用就要注意一些。

举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	p = (int*)realloc(p, 80);//注意这里!这样写可以吗?
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	free(p);
	p = NULL;
	return 0;
}

观察代码中的思考问题,这样写合理吗?

不合理!

        想象以下,如果realloc空间开辟失败,就会传给p一个空指针,但是p本身是有指向malloc开辟的那块地址啊,一旦接收空指针,原来的那块空间不就废掉了吗?

        所以可以考虑用个临时指针来接收,如果realloc开辟成功,再将临时指针赋值给p,就可以有效避免内存泄漏问题 ;

代码实现如下:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	int* tmp_p = (int*)realloc(p, 80);
	if (tmp_p != NULL)
	{
		p = tmp_p;
		//处理
	}
	else
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

3. 常见的动态内存错误

对NULL指针的解引用操作

直接上代码:

int main()
{
	int* ptr = (int*)malloc(INT_MAX * INT_MAX);
	*ptr = 10;
	return 0;
}

        想象以下如果malloc开辟失败会发生什么?

        malloc开辟失败返回一个NULL,赋值给ptr

        造成对NULL指针的解引用操作!

对动态开辟空间的越界访问

        看看以下代码哪里出问题了?

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(ptr + i) = i;
	}
    free(ptr);
    ptr = NULL;
	return 0;
}

很明显,当赋值10的时候发生了越界访问;

对非动态开辟内存使用free释放

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*ptr = i;
		ptr++;
	}

	free(ptr);
	ptr = NULL;

	return 0;
}

 这样是否可行?

        ptr一旦++,导致ptr指针不在指向动态开辟的起始位置,这时候free会发生什么呢?(如下图)

【C语言】零基础教程——动态内存管理

这时候有人可能会说:那用const修饰以下不就很安全吗?

        是啊,安全是安全了,但是ptr = NULL;不也不行了吗?从而产生野指针问题,所以一定要注意避免!

 

使用free释放一块动态开辟内存的一部分

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

对同一块动态内存多次释放

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    free(ptr);

    //处理

    free(ptr);//重复释放
    return 0;
}

 动态开辟内存忘记释放(内存泄漏)

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    return 0;//忘记free
}

注        意:动态开辟的空间一定要释放,并且正确释放!

【C语言】零基础教程——动态内存管理 

码字不易~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/124382.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!