欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

对动态内存分配函数malloc、calloc、realloc、free的理解

程序员文章站 2022-05-12 09:17:44
...

动态内存分配:

  动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
  以一个数组为例:在定义数组时给定了内存空间,如果一开始给定的内存空间足够大,就能够放入需要放入的所有数据元素,但如果给定的内存空间小于所需要放入的数据元素个数时,我们就需要给这个内存空间扩容,以满足放入所有数据元素的需求。

  在扩容过程中,我们需要注意以下两个问题:

  1. 如果增容空间过大而数据元素很少,就会导致内存空间浪费;
  2. 如果增容空间不足而数据元素较多,就会导致内存溢出现象。

动态内存分配函数有:

  • malloc函数:

  malloc的全称是memory allocation,中文名为动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。
  函数原型为:void *malloc(unsigned int size);,其作用是在内存的动态空间中分配一个长度为size的连续空间。此函数的返回值是分配区域的起始地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
  如果分配成功则返回指向被分配内存的指针(此空间中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。

  • calloc函数:

  calloc的全称是clear allocation,中文名为动态内存分配并清零,···。
  函数原型为:void *calloc(unsigned int num,unsigned int size);,···。
  如果分配成功则返回指向被分配内存的指针(此空间中的初始值为0),否则返回空指针NULL,···。

  • realloc函数:

  realloc的全称是reset allocation,中文名为动态内存调整,···。
  函数原型为:extern void *realloc(void *mem_address, unsigned int newsize);,···。
  先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(原来的指针会自动释放,不需要再使用free),同时返回新分配的内存区域的首地址。重新分配成功返回指向被分配内存的指针,否则返回空指针NULL。
  注意:调整后的大小可大可小(如果新的大小大于原内存大小,新分配部分不会被初始化;如果新的大小小于原内存大小,可能会导致数据丢失)。

  • free函数:

  函数原型为:void free(void *ptr);,一般使用malloc,calloc,realloc函数进行内存分配后要使用free(起始地址的指针) 对内存进行释放,不然内存申请过多会影响计算机的性能,以至于重启电脑。但是若使用动态内存分配函数后未使用free函数进行释放,还可以使用指针对该块内存进行访问,如果释放则不能再访问。
  注意:使用后该指针变量一定要重新指向NULL,防止野指针出现,有效规避错误操作。

(以上函数都被包含于头文件stdlib.h中)

总结:

  1. malloc函数可以分配指定字节数的空间,但该空间中的初始值不确定
  2. calloc函数可以分配指定长度的空间,该空间中的初始值都被初始化为0
  3. realloc函数可以更改以前空间的长度(可以增加也可以减少)
malloc与calloc的区别:

  对于malloc函数,其原型为void *malloc(unsigned int size);,只有一个参数,size为我们要申请的空间大小,需要手动计算,比如我们要申请10个int类型的空间:int *p = (int*)malloc(10 * sizeof(int)),如果编译器默认int为4个字节进行存储的话,那么计算结果为40byte,即申请了一个40byte的连续空间,并强制转换为int类型,赋值给指针p,但此时申请到空间里的初始值是不确定的。
  而对于calloc函数,其原型为void *calloc(unsigned int num,unsigned int size);,比malloc函数多一个参数num,因此它不需要人为计算空间大小,比如我们要申请10个int类型的空间:int *pp = (int*)calloc(10, sizeof(int)),这样写就能省去人为空间计算这一步骤。
  但是,是否省去人为空间计算并不是他们最主要的区别,最主要的区别是malloc申请空间后空间里的值是随机的,并没有进行初始化,而calloc在申请空间后会对空间内的数据进行逐一初始化,并将初始值设置为0。

  • 代码如下:
#include "stdio.h"
#include "malloc.h"
int main()
{
	int i;
	int *p = (int*)malloc(10 * sizeof(int));
	int *pp = (int*)calloc(10, sizeof(int));
	printf("malloc申请的空间值:\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	printf("\n");
	printf("calloc申请的空间值:\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", pp[i]);
	}
	free(p);
	free(pp);
	return 0;
}
  • 运行如下:

对动态内存分配函数malloc、calloc、realloc、free的理解
  既然calloc函数不需要人为计算空间大小,并且可以直接将内存空间的数据初始化为0从而避免错误,那为什么不直接使用calloc函数?malloc函数存在的意义又是什么?
  我们知道,事物都具有两面性。对于这两个函数来说,由于calloc函数要将空间内的每一个数据都初始化为0,那么运行效率较malloc函数必然会大大降低,实际过程中大多数情况的空间申请也是不需要对数据进行初始化的,这也就是malloc函数存在的意义。

realloc与malloc及calloc的区别

  说完了malloc函数和calloc函数,接下来谈谈realloc函数。realloc函数和上面两个函数有本质区别,其原型为extern void *realloc(void *mem_address, unsigned int newsize);,用于对动态内存进行扩容,即已申请的动态空间不够用,其中mem_address为指向原空间地址的指针,newsize为接下来扩充的容量大小。

  • 代码如下:

#include "stdio.h"
#include "malloc.h"
int main()
{
	const int size = 100;
	int *p = (int*)malloc(10 * sizeof(int));
	int *pp = (int*)realloc(p, size * sizeof(int));
	printf("原Address:%d\n新Address:%d\n", p, pp);
	free(pp);
	return 0;
}
  • 运行如下:

对动态内存分配函数malloc、calloc、realloc、free的理解
  可从图看出,扩容后地址和原先地址是不一样的,这取决于扩容的内存大小。如果size较小,原来申请的动态内存后面还有空余内存,系统将直接在原内存空间后面扩容,并返回原动态空间的地址;如果size较大,原来申请的空间后面没有足够大的空间扩容,系统将重新申请一块(10+size)*sizeof(int)的内存,并把原来空间的内容拷贝过去,原空间free;如果size非常大,则内存空间申请失败,返回空指针NULL,原来的内存不会释放。
  注意:如果扩容后的内存空间较原空间小,将会出现数据丢失,如果直接realloc(p, 0),相当于free。