• 动态内存开辟(上)


    一. 为什么会存在动态内存分配

    我们已经掌握了一下的内存开辟方式

    	int a = 20; //在栈区上开辟四个字节
    	char arr[20] = { 0 }; //在栈区上开辟10个字节的连续空间 
    

    但是上述开辟空间的方式有两个特点

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

    特点1很好理解

    就像上面 我们指定数组arr之后 在栈区上就不可以改变数组的大小了

    但是我们使用malloc指定内存的大小之后 还是可以使用realloc来重置大小

    特点2 我们使用一段代码来解释下

    在这里插入图片描述

    我们发现 动态开辟内存可以不指定数组的长度 而是传递进去一个变量

    有时候对于空间大小的需求 我们只有在程序运行的时候才能知道

    这个时候我们就只能使用动态内存开辟了

    二. 动态内存函数的介绍

    1. malloc和free

    1.1 malloc

    c语言提供了一个动态内存开辟的函数:

    void* malloc (size_t size);
    

    这个函数像内存申请一块连续可用的空间 并且返回指向这块空间的指针 是一个无符号类型的指针

    这其中有四个注意点

    1 如果开辟成功,则返回一个指向开辟好空间的指针。
    2 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
    3 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己
    来决定。
    4 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

    特点一说明了这个函数的返回值

    特点二说明了它开辟失败的情况

    特点三详细介绍了它返回值的类型

    特点四说明了如果开辟的空间为0 这个时候malloc会做什么

    这里c语言提供了另一个函数 专门史用来做动态内存的释放和回收的

    void free (void* ptr);
    

    1.2 free

    free函数也有两个特点

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

    这里特别要注意特点1 我们来看一段代码

    int main()
    {
    	int a = 10;
    	int* p = &a;
    
    
    
    	free(p);
    	p = NULL;
    	return 0;
    }
    

    如果我们写出来上面这一段代码 那么我们的编译器会给我们报错

    在这里插入图片描述

    特点二是一个知识点 我们记一下就好了

    指针指向NULL的时候 free什么都不做

    int main()
    {
    	int a = 10;
    	int* p = &a;
    
    
    	p = NULL;
    	free(p);
    	return 0;
    }
    

    在这里插入图片描述

    1.3 完整的演示代码

    int main()
    {
    	// 代码1
    	int num = 0;
    	scanf("%d", &num);
    	int arr[50] = { 0 };
    
    	//代码2
    	int* p = (int*)malloc(num * sizeof(int));
    	if (NULL == p)
    	{
    		perror(malloc);
    	}
    	else
    	{
    		int i = 0;
    		for ( i = 0; i < num; i++)
    		{
    			*(p + i) = i;
    		}
    	}
    	free(p);
    	p = NULL;
    	return 0;
    }
    

    2. calloc

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

    原型如下

    void* calloc (size_t num, size_t size);
    

    函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
    与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

    int main()
    {
    	int* p = (int*)calloc(40, sizeof(int));
    	if (NULL==p)
    	{
    		perror(calloc);
    	}
    	free(p);
    	p = NULL;
    	return 0;
    }
    

    代码如上

    我们来看看它们的内存

    在这里插入图片描述

    我们可以发现全部初始化内存确实为0

    3. realloc

    1 realloc函数的出现让动态内存管理更加灵活。
    2 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
    候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
    的调整。

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

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

    在这里插入图片描述

    情况一 如果说原有空间之后又足够大的空间 那么就直接扩展内存追加空间 原理啊空间的数据不发生变化

    情况二 当情况2的时候 原有空间之后没有足够多的空间 扩展的方法是 在堆空间上另外寻找一个合适大小的连续空间来使用 这样函数返回的是一个新的内存地址

    int main()
    {
    	int* p = (int*)malloc(100);
    	if (p==NULL)
    	{
    		perror(malloc);
    	}
    	else
    	{
    		;
    	}
    	// p = (int*)realloc(p, 1000);
    	// 这个写法因为有可能 realloc开辟内存失败 这样的话返回一个空指针
    	// 开辟的内存空间就消失了 
    	// 所以说 我们赋值前要判断是否为空指针
    	int* ptr = (int*)realloc(p, 1000);
    	if (ptr==NULL)
    	{
    		perror(realloc);
    	}
    	else
    	{
    		p = ptr;
    	}
    
    	free(p);
    	p = NULL;
    	return 0;
    }
    

    三. 常见的动态内存错误

    3.1 对NULL指针的解引用操作

    void test()
    {
    	int* p = (int*)malloc(INT_MAX / 4);
    	*p = 20;//如果p的值是NULL,就会有问题 
    	free(p);
    }
    

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

    int main()
    {
    	int* p = (int*)malloc(10);
    	if (p==NULL)
    	{
    		perror(malloc);
    	}
    	else
    	{
    		int i;
    		for ( i = 0; i <= 10; i++)
    		{
    			*(p + i) = i;
    		}
    	}
    	free(p);
    	p = NULL;
    }
    

    在这里插入图片描述

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

    int main()
    {
    	int a = 10;
    	int* p = &a;
    	free(p);
    }
    

    在这里插入图片描述

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

    int main()
    {
    	int* p = (int*)malloc(40);
    	p++;
    	free(p);
    	return 0;
    }
    

    在这里插入图片描述

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

    int main()
    {
    	int* p = (int*)malloc(40);
    	free(p);
    	free(p);
    	return 0;
    }
    

    在这里插入图片描述

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

    int main()
    {
    	while (1)
    	{
    
    		int* p = (int*)malloc(20);
    		if (NULL == p)
    		{
    			perror(malloc);
    		}
    		else
    		{
    			*p = 20;
    		}
    	}
    	return 0;
    }
    

    以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够

    不吝赐教 在评论区或者私信指正 博主一定及时修正

    那么大家下期再见咯

  • 相关阅读:
    从硬件到软件:揭秘磁盘结构和文件系统组织
    ElasticSearch索引基本查询语法
    大华海康NVR录像JAVA下载及WEB播放
    <图像处理> 图像插值算法
    数组和List相互转化(摒弃循环)
    Gitee配置静态页面
    行内元素和块级元素有什么区别?语义化作用
    “客户端到服务器的数据传递”和“服务器上的数据传递”这两种数据传递的方式的区别
    从IPC到分布式软总线的随笔
    Visual Studio 2019 VS编译ffmpeg4.4为静态库或动态库
  • 原文地址:https://blog.csdn.net/meihaoshy/article/details/127076814