首页 > 系统相关 >【C语言】动态内存管理函数的 深度解析 #是不是对数组不能变大变小而烦恼呢?学会动态内存管理函数,消去数组耿直的烦恼#

【C语言】动态内存管理函数的 深度解析 #是不是对数组不能变大变小而烦恼呢?学会动态内存管理函数,消去数组耿直的烦恼#

时间:2023-06-02 17:38:20浏览次数:45  
标签:tmp malloc 动态内存 int 烦恼 free 数组 空间 开辟

前言

  • 动态内存管理函数可以说很好用,但是有些小危险
  • 所谓动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。 动态内存分配不像 数组 等 静态内存 分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
  • 动态内存函数的头文件都是:<stdlib.h>

为什么存在动态内存分配?

我们已经掌握的内存开辟方式有:

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

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

  1. 空间开辟大小是固定的。
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

此外:在后面的通讯录的完整实现,以及数据结构的完整实现大都是需要动态内存来实现的。

mallocfree

1.malloc

malloc是C语言提供的一个内存开辟函数,该函数的参数如下:

在这里插入图片描述

在这里插入图片描述

<font color=blue>返回值:

在这里插入图片描述

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

malloc开辟的内存空间都是每有初始化的,观察内存如下:

在这里插入图片描述

2.free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数参数如下:

在这里插入图片描述 在这里插入图片描述

  • free函数用来释放动态开辟的内存。
  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptrNULL指针,则函数什么事都不做。

注意:任何只要是开辟的动态内存空间(堆上的),都要free释放返还给操作系统。

3.使用

mallocfree是要共同使用的,有malloc开辟空间就一定要有free释放空间,通过上面的函数介绍,接下来结合使用。

例如,这里动态开辟一个能够存放10个整型的数组:

#include <stdio.h>
#include <stdlib.h> // 对应头文件

int main()
{
    // 因为返回的是void*,最好强转以下
	int* tmp = (int*)malloc(sizeof(int) * 10); // 也可以直接放一个40(要40字节)

	// 一定要检查开辟成功没有
	if (tmp == NULL)
	{
		perror("malloc fail");  // 这里打印错误“开辟失败”
		exit(-1);  // 这里可以理解为直接退出程序
	}

	// 开辟没问题,进行以下操作
	// 给开辟的数组赋值
	for (int i = 0; i < 10; ++i)
	{
		tmp[i] = i + 1;
	}

	// 打印
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", tmp[i]);
	}

	// 操作完后一定要释放空间
	// 传递指向那段空间起始位置的指针
	free(tmp);
	// 释放后要把该指针置为空,不然后面一不小心又使用该指针找到那块空间,属于非法访问了
	tmp = NULL;  

	return 0;
}

如果后面不释放,虽然现在的机器大都会自动返还给操作系统,但是出于严谨和安全,一定要记得free,不然会造成内存泄露问题,这是很严重的。

calloc

calloc也是动态内存分配函数 在这里插入图片描述

// 例如这里开辟一个有十个整型元素的数组
int* arr = (int*)calloc(10, sizeof(int));

在这里插入图片描述

在这里插入图片描述

通过上面的介绍,可以发现,calloc的功能与malloc几乎相同,其有两点不同之处:

  1. callocmalloc的函数参数不同;
  2. calloc开辟的空间会将全部元素初始化0,而malloc则是随机值。

如:

#include <stdio.h>
#include <stdlib.h> // 对应头文件

int main()
{
	//               个数     一个元素的大小
	int* tmp = calloc(10, sizeof(int)); 

	// 一定要检查开辟成功没有
	if (tmp == NULL)
	{
		perror("calloc fail");  // 这里打印错误“开辟失败”
		exit(-1);  // 这里可以理解为直接退出程序
	}

	// 开辟没问题,进行以下操作
	// 给开辟的数组赋值
	for (int i = 0; i < 10; ++i)
	{
		tmp[i] = i + 1;
	}

	// 打印
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", tmp[i]);
	}

	// 操作完后一定要释放空间
	// 传递指向那段空间起始位置的指针
	free(tmp);
	// 释放后要把该指针置为空,不然后面一不小心又使用该指针找到那块空间,属于非法访问了
	tmp = NULL;

	return 0;
}

在这里插入图片描述

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc

realloc函数的出现让动态内存管理更加灵活。

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

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

基础点:

  1. ptr 是要调整的内存地址。
  2. size 调整之后新大小。
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  • <font size=5>realloc在调整内存空间的是存在两种情况:

情况1: 内存中原有的空间之后有足够的空间来存放重新开辟的新大小的空间,这时直接在原有的空间之后追加空间。 情况2: 内存中原有的空间之后没有足够的空间来存放重新开辟的新大小的空间,这时在堆上另找一个合适大小的连续空间来使用。

在这里插入图片描述

那么我们如何来写代码呢?

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

int main()
{
	int* tmp = (int*)malloc(100);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//扩展容量
	//代码1
	tmp = (int*)realloc(tmp, 1000);//这样可以吗?(如果申请失败会如何?)

///////////////////////////////////////////////////////////////////////////////
	
	//代码2
	int* p = realloc(tmp, 1000);
	if (p == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}
	tmp = p;
	
	// 释放
	free(tmp);
	tmp = NULL;

	return 0;
}
  • 上面有两种写法,代码1跟代码2
  1. 分析代码1:如果重新开辟的空间开辟成功,并且是在原空间上做修改,那么这是可行的;如果原空间后面没有足够空间来开辟,另寻找到一份空间来存放,此时的地址空间的起始地址发生了改变,如果空间申请失败,而此时又将该空间的起始地址给了原有的指针变量tmp,这时原有空间就找不到了,并且会出现错误,所以还是不严谨的;
  2. 分析代码2:代码2是先将重新开辟的空间的起始地址交给一个临时变量,在判断这份空间的有效性,最后才赋值给原有的指针变量,这样做才是最安全且不会亏损原有空间的,所以,根据代码2的严谨性强的特点,以后realloc一定要写代码2这种样式。

有了reallocbuff的加持,我们想让数组变他就嘚变,哈哈哈

常见的动态内存错误

1.对NULL指针的解引用操作

void test()
{
	 int *p = (int *)malloc(INT_MAX/4);
	 // 这里没有判断是否开辟成功
	 *p = 20;  //如果p的值是NULL,就会有问题
	 free(p);
	 p = NULL;
}

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

void test()
{
	 int i = 0;
	 int *p = (int *)malloc(10*sizeof(int));
	 if(NULL == p)
	 {
	     exit(-1);
	 }
	 
	 for(i = 0; i <= 10; i++)
	 {
	     *(p+i) = i;//当i是10的时候越界访问
	 }
	 
	 free(p);
	 p = NULL;
}

i10就越界访问了,越界访问的后果就不用多说了把(哈哈哈哈哈,非法闯入)。

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

void test()
{
	 int a = 10;
	 int *p = &a;
	 free(p);  //ok?
	 p = NULL;
}

free是不能释放除动态开辟的内存以外的内存的,只适用于堆上。

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

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

free这样子释放相当于拦腰截断,会存在内存泄漏的问题。

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

void test()
{
	 int *p = (int *)malloc(100);
	 free(p);
	 free(p);//重复释放
	 p = NULL;
}

对同一块空间多次释放,这当然是不行的。

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

这样是绝对不行的,内存泄漏迟早会吃光你的内存。

例如:

void test()
{
	 int *p = (int *)malloc(100);
	 if(NULL != p)
	 {
	 	*p = 20;
	 }
}
int main()
{
	 test();
	 // p指向的动态内存空间没有释放,虽然p变量销毁了,但申请的空间还在
	 return 0;
}

写在最后

动态内存分配是不是很容易就学会了,接下来就可以 ”肆无忌惮“ 的玩弄 ”数组“ 了,不过要小心内存泄漏噢!

感谢阅读本小白的博客,错误的地方请严厉指出噢!

标签:tmp,malloc,动态内存,int,烦恼,free,数组,空间,开辟
From: https://blog.51cto.com/u_16019252/6404268

相关文章

  • lucene底层数据结构——FST,针对field使用列存储,delta encode压缩doc ids数组,LZ4压缩算
    参考:http://www.slideshare.net/lucenerevolution/what-is-inaluceneagrandfinalhttp://www.slideshare.net/jpountz/how-does-lucene-store-your-data摘录一些重要的:看一下Lucene的倒排索引是怎么构成的。我们来看一个实际的例子,假设有如下的数据: docid年龄性别118女220女318男 ......
  • 【web 开发】PHP8中对数组操作的新变化
    自动创建元素的顺序改变在PHP8中,引用赋值时,自动创建的数组元素或者对象属性的顺序和PHP7版本相比发生了变化,下面我们通过例子来体验下变化在哪里.<?php$array=[];$array['a']=&$array['b'];$array['b']=1;echo"\n";var_dump($array);?>执行结果如下:这个结果是PHP8......
  • 判断数组内所有属性均相等
     if( this.data.orderList.every(item=>item.obligationTime===this.data.orderList[0].obligationTime)){        console.log('全等')        this.data.flag=true        clearInterval(this.data.timer)      }else{        co......
  • 560. 和为 K 的子数组
    思路难度中等1936给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。 示例1:输入:nums=[1,1,1],k=2输出:2示例2:输入:nums=[1,2,3],k=3输出:2 提示:1<=nums.length<=2*104-1000<=nums[i]<=1......
  • Vue修改数组、对象并且触发视图更新的方法以及原理
    一、数组 items:['a','b','c'];//一个普通的数组this.items[1]='x';//修改已有项this.items[3]='d';//新增一项this.item.length=2;//修改数组的长度//一个对象数组msg:[{id:1,selected:true,title:'aaa',},{i......
  • 划分数组专题
    一般采用回溯法思想,但需要将问题进行转化,同时采用动态规划减小时间复杂度1.分割等和子数组2.目标和3.划分k个相等的子集4.三等分......
  • [Python]-numpy模块-反转数组的三种方法
    给定一个一维numpy数组,反转数组的三种方法:切片索引numpy.flipud()函数numpy.flip()函数importnumpyasnpa=np.array([1,2,3,4,5])#切片索引ans1=a[::-1]print(ans1)#[54321]#numpy.flipud()函数ans2=np.flipud(a)print(ans2)#[54321]#n......
  • leetcode 65. 有效数字 66. 寻找两个正序数组的中位数
    leetcode65.有效数字66.寻找两个正序数组的中位数65.有效数字难度困难295有效数字(按顺序)可以分成以下几个部分:一个小数或者整数(可选)一个'e'或'E',后面跟着一个整数小数(按顺序)可以分成以下几个部分:(可选)一个符号字符('+'或'-')下述格式之一:至少一位数字,后面跟着一个......
  • python 中json数据可以以类似数组的方法访问 print(j["age"]["nian"])
    使用Python处理Json数据-猫坚果NutCat-博客园(cnblogs.com)JSON模组的常用方法load/loads:把JSON转换为Python#somejsonsomebody_info='{"name":"WenjieYe","age":75,"nationality":"China"}'#parse......
  • php数组排序原理
    以下是一个使用PHP中的sort函数对数组进行排序的示例代码:$fruits=array("apple","banana","orange");sort($fruits);print_r($fruits);在此示例中,我们使用sort函数对$fruits数组进行升序排序。结果输出为["apple","banana","orange"]。PHP中的数组排序函数通常使用......