【熟视C语言】C语言动态内存管理(malloc,calloc,realloc,free)

2023-06-23 14:34:04 浏览数 (2)

写在前面

本篇文章为动态内存函数的使用详解,希望对你的动态内存函数学习有所帮助。

为什么需要使用动态内存

对于初学者来说,最先接触到的内存使用便是以下场景:

代码语言:javascript复制
//
int val = 3;//为变量val在栈区上申请一块空间存储数据
char str[] = "abc";//为数组str在栈区上申请一块空间存储数据

这样的空间开辟方式,在后续操作中,是无法改变以上数据所占空间大小的,并且对于数组来说,开辟空间是必须指明数组长度的。而在我们实际生活中又确实会出现一组数据量会随时变化的数据组。这时我们就需要使用动态内存函数来为数组,变量来开辟空间。

动态内存函数

(函数声明在头文件stdlib.h中)

malloc和free

malloc是C语言提供的一个开辟动态内存的函数。

代码语言:javascript复制
void* malloc (size_t size);

这个函数向内存申请一块在堆区上连续可用的空间,并返回指向该空间的指针。

  • 开辟成功会返回指向开辟好的空间的指针,失败则返回NULL指针。
  • 返回值的类型是void*指针,具体使用时只需要对返回的指针进行强制类型转换即可。
  • 在标准中malloc并未对size是0的情况进行规定,具体情况看编译器。

同时,C语言提供另外一个函数free,专门用于释放和回收动态内存。

代码语言:javascript复制
void free (void* ptr);

free函数接收一个指向一块开辟好的动态内存空间,释放并回收这块J空间。

  • 标准对参数ptr指向的空间不是动态开辟的这个行为并没有做出规定。
  • 如果接收到的ptr是NULL指针,函数不会进行任何操作。
代码语言:javascript复制
int main()
{
	int n = 10;
	int* array = (int*)malloc(sizeof(int) * n);//开辟n个整型数据大小的连续空间
	if (array == NULL)//检测是否申请失败
	{
		perror("malloc failed");//发出失败提示
		exit(-1);//运行失败,结束程序
	}

	for (int i = 0; i < n; i  )
	{
		array[i] = i;//此时当作数组使用
	}

	free(array);//释放动态内存
	array = NULL;//对该指针置空,防止非法访问内存空间(野指针)

	return 0;
}

calloc

malloc外,C语言还提供了一个函数calloc用于动态内存分配。

代码语言:javascript复制
void* calloc (size_t num, size_t size);
  • 函数的功能是开辟num个大小为size的空间
  • malloc不同的是,calloc会将申请到的空间的每个字节初始化为0
代码语言:javascript复制
int main()
{
	int n = 10;
	int* array = (int*)calloc(n, sizeof(int));//申请n个整型大小的内存空间
	if (array == NULL)//检测是否申请失败
	{
		perror("calloc failed");//发出失败提示
		exit(-1);//运行失败,结束程序
	}

	for (int i = 0; i < n; i  )
	{
		printf("%d ", array[i]);//此时打印,是已初始化的数据,全零
	}
	printf("n");

	free(array);
	array = NULL;

	return 0;
}

(代码运行截图)

realloc

仅有以上的函数要实现真正的动态地使用一块内存空间还是不够的。以上函数功能仅仅是申请和释放一块动态内存,而我们还需要一块改变动态内存大小的函数,这个函数就是realloc

代码语言:javascript复制
void* realloc (void* ptr, size_t size);
  • ptr指向需要调整的内存空间的地址。
  • size是调整之后的大小。
  • 返回值为一个指向调整之后的空间起始地址的void*的指针。
  • 如果申请失败会返回一个空指针,并且不会自行释放原先的空间。
  • realloc在调整内存空间大小时存在两种情况:
    • 一:在原有空间之后又足够大的空间(即没被其他数据占用)。 这种情况直接原地扩容,追加原有数据后方的空间且不对原有数据做出改动。
    • 二:原有空间之后空间不够大。 这种情况realloc函数会在堆的其他位置上找一块总够大的空间,将原有数据拷贝进去,并且会自行释放原来占用的空间,最后返回的地址是一个新的地址。
代码语言:javascript复制
int main()
{
	int n = 10;
	int* array = (int*)calloc(n, sizeof(int));//申请n个整型大小的内存空间
	if (array == NULL)//检测是否申请失败
	{
		perror("calloc failed");//发出失败提示
		exit(-1);//运行失败,结束程序
	}

	//危险的操作
	//array = (int*)realloc(array, 12);//由于申请失败时不会自行释放原空间,而此代码将原先指向原空间的指针置空,无法再找回原空间并释放(内存泄露)
    
    
	//安全的操作
	int* ptr = NULL;
	ptr = (int*)realloc(array, 12);
	if (ptr != NULL)//检测是否扩容失败
	{
		array = ptr;//扩容成功再赋值回来
	}
	
	//...

	free(array);
	array = NULL;

	return 0;
}

动态内存函数常见使用错误

由于动态内存函数地使用涉及指针,内存空间的知识,对于C语言这块内容还不是很熟悉的人来说使用难度较大。这里总结几个比较常出现的错误,希望对你的使用有所帮助。

对NULL指针的解引用

代码语言:javascript复制
void test1()
{
	int* ptr = (int*)malloc(sizeof(int));
	//如果malloc申请空间失败那么此时ptr就是NULL
	*ptr = 9;//此时就会发生
}

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

代码语言:javascript复制
void test2()
{
	int* ptr = (int*)malloc(10 * sizeof(int));
	if (ptr == NULL)
		exit(-1);

	for (int i = 0; i <= 10; i  )
		ptr[i] = i;//当i==10的3时候发生越界

	free(ptr);//值得注意的是,动态内存空间的越界并不会直接检测出来,而是会在free的时候检测出来并报错
	//此时会报出类似堆区异常访问,或者在访问正常数据后的空间之类的错误
}

(代码运行截图)

使用free释放非动态开辟的内存空间

代码语言:javascript复制
void test3()
{
	int a[10] = { 0 };
	int* p = &a;
	//...
	free(p);//报错
}

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

代码语言:javascript复制
void test4()
{
	int* ptr = (int*)malloc(sizeof(int));
	if (ptr == NULL)
		exit(-1);
	//...
	free(p);
	free(p);//重复释放
}

只释放一部分动态内存空间

代码语言:javascript复制
void test5()
{
	int* ptr = (int*)malloc(10 * sizeof(int));
	if (ptr == NULL)
		exit(-1);
	//...
	p  ;
	//...
	free(p);//只释放了一部分内存
}

忘记释放动态内存空间(内存泄漏)

代码语言:javascript复制
void test6()
{
	int* ptr = (int*)malloc(10 * sizeof(int));
	if (ptr == NULL)
		exit(-1);
	//...
	//内存泄露
}

结语

非常感谢各位读者能读完这篇文章,如果你觉得做的还不错的话,可以点赞收藏分享,让更多的朋友知道,当然,如果你觉得有什么问题的话也欢迎在评论区留言或私信告诉我哦!下期再会!

彩蛋

源码在这: gitee-test分支-动态函数详解文件 GitHub-master-Dynamic memory.c

0 人点赞