动态内存管理

本文详细介绍了动态内存管理的重要性,包括malloc、free、calloc和realloc函数的用法,以及内存泄漏的概念和典型面试题分析。还展示了如何为一维和二维数组动态分配内存,强调了内存管理的规则和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 为什么要有动态内存管理

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

这是在栈上开辟的内存空间,一旦定义了,所开辟的内存空间就固定了,后续是无法修改内存空间的大小,但在实际应用中,我们定义时,大部分情况并不知道所需要开辟内存空间的大小。这时,就需要借用动态内存开辟了,这样,就可以自己按需要开辟空间,值得注意的是,动态开辟是在堆区开辟空间的,所以,动态开辟的空间后续如果不使用,一定要释放掉!

注:malloc,free,calloc,realloc都需要包含头文件stdlib!

2. malloc和free

2.1 malloc

函数malloc是用来开辟空间的,其函数原型如下:
在这里插入图片描述
这个函数向堆区申请了一块连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回一个指向开辟好空间的指针
  • 如果开辟失败,则返回NULL,因此当我们动态开辟空间后,需要检查是否开辟成功
  • 默认返回类型为泛型指针void*,这也就意味着后续可以根据自己需要将其转化为其他类型的指针
  • size为0是未定义的行为,取决于编译器

2.2 free

在开头,我们就了解到了,动态开辟的内存的空间后续如果不使用了,一定要手动释放掉!用malloc函数动态开辟空间,用什么来释放内存呢?答案就是free,free的函数原型如下:
在这里插入图片描述
注意:

  • 这里的ptr一定要是申请内存空间的首地址,必须是起始地址!
  • free只能释放动态开辟的空间
  • 如果ptr是NULL,那么free什么都不做

举个例子:

int main()
{
	int* arr = (int*)malloc(4 * sizeof(int));//开辟四个整型空间
	if (arr == NULL)
	{
		perror("malloc");
		exit(1);
	}
	for (int i = 0; i < 4; i++)
	{
		*(arr + i) = i;
		printf("%d ", *(arr + i));
	}
	free(arr);//一定要记得释放,且要使用动态开辟空间的起始地址!
	arr = NULL;//释放完空间后,该空间的使用权限已经还给了操作系统了,
			  //arr无权访问这篇空间了,也就需要将arr置为空指针!
	return 0;
}

运行结果:
在这里插入图片描述

3. calloc和realloc

3.1 calloc

除了malloc,C语言还提供了一个动态开辟内存空间的函数叫calloc,其原型如下:
在这里插入图片描述

  1. calloc函数的功能和malloc唯一的区别就是,calloc会将所开辟内存空间的每个字节初始化为0
  2. calloc函数的参数与malloc不一样,num表示需要开辟空间的个数,size表示需要开辟的一个空间的大小
    在这里插入图片描述
    注意:参数里colloc是 *,malloc是 ,

话不多说,直接来看实例:

int main()
{
	int* arr = (int*)calloc(4,sizeof(int));//开辟四个整型空间
	if (arr == NULL)
	{
		perror("malloc");
		exit(1);
	}
	//未赋值前
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", *(arr + i));
	}
	printf("\n");
	//赋值后
	for (int i = 0; i < 4; i++)
	{
		*(arr + i) = i;
		printf("%d ", *(arr + i));
	}
	free(arr);
	arr = NULL;
	return 0;
}

运行结果:
在这里插入图片描述
在日常使用中malloc使用居多,如果需要给申请的内存空间初始化才使用calloc

3.2 realloc

有时候,我们在后续中发现之前动态申请的空间不够了或者太大了,需要对申请的动态内存进行调整,这时就可以使用realloc函数来调整动态申请的空间,realloc函数原型如下:

在这里插入图片描述
注:

  • ptr表示需要调整的动态内存空间的起始地址
  • size表示调整之后内存空间的新大小

补充: realloc除了可以实现灵活调整动态内存空间的大小,还可以实现类似于malloc的功能

realloc(NULL, size) == malloc(size) //两者等价!

传空指针给realloc,realloc就没法调整空间,就会帮你新开辟一块空间,大小为size

realloc调整动态空间时有下面3种情况

  1. 如果原有空间之后有足够的空间,就在原有空间之后在接上一块空间即可,原有空间的内容不改变,返回原有空间的起始地址
  2. 如果原有空间之后没有足够的空间,就在堆空间上另找一个合适大小的连续空间,再将原来空间的数据拷贝一份到新的空间,然后再释放旧的空间,返回新的内存空间的起始地址
  3. 调整失败,返回的是NULL

由于realloc调整空间时可能存在新开辟一块堆空间,使用realloc就需要注意一些

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

int main()
{
	int* arr = (int*)malloc(4 * sizeof(int));//开辟四个整型空间
	for (int i = 0; i < 4; i++)
	{
		*(arr + i) = i;
		printf("%d ", *(arr + i));
	}

	//代码1-直接将realloc的返回值放到arr中
	arr = (int*)realloc(arr, 8 * sizeof(int));

	//代码2-先将realloc的返回值放在临时指针变量temp中,如果返回值不为空,再将temp赋给arr
	int* temp = (int*)realloc(arr, 8 * sizeof(int));
	if (temp == NULL)
	{
		perror("realloc");
		exit(1);
	}
	arr = temp;

	free(arr);//一定要记得释放,且要使用动态开辟空间的起始地址!
	arr = NULL;//释放完空间后,该空间的使用权限已经还给了操作系统了,arr无权访问这篇空间了,也就需要将arr置为空指针!
	return 0;
}

代码1是不对的,请你思考以下,如果realloc调整失败会怎样?答案:arr原来指向的空间也找不到了,会造成数据的丢失!因此,代码1的处理方式是欠妥的,最好的方式是使用代码2!

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

#include <stdio.h>

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	test();
	while (1);
}

仔细分析这段代码,不难发现动态申请的空间没有释放,可能会导致内存泄漏

内存泄漏是指在程序运行过程中,动态分配的内存空间在不需要时没有被正确释放的过程情况。当程序中存在内存泄漏时,这些未释放的内存空间会一直占用系统资源,导致系统的可用内存逐渐减少,最终可能导致程序性能下降甚至崩溃

因此,在动态开辟空间后尽量做到:

  1. 谁申请的空间谁释放
  2. 如果不能释放,要告诉使用的人,记得释放

5. 动态内存经典笔试题分析

5.1 题目1

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}

void Test()
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

这道题存在的问题:

  1. 内存泄漏
  2. 程序崩溃

在这里插入图片描述

  • GetMemory(str)是传值调用,GetMemory函数不影响str的值,str依然为NULL,而在strcpy函数中必定会涉及到对str的解引用操作,这会引起程序的崩溃!
  • 在GetMemory函数结束后,指针变量p自身所占的栈区空间会被释放,而p所指向的动态内存空间(堆区)依然存在,这会导致无法找到动态开辟的空间,也就会造成内存泄漏的问题

可以更改为以下的代码:

char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

5.2 题目2

char* GetMemory()
{
	char p[] = "hello world";
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

这道题是返回栈空间地址的问题

  1. p数组存放在栈区,当GetMemory函数结束后,p数组所占的内存空间就被释放了,这意味着内存空间中的内容也被清除了
  2. 等GetMemory函数返回后,使用str指针去访问p数组,就是非法访问了,因为p数组的内存已经还给操作系统了(注:还给了操作系统是没有这块空间的使用权限,但这块空间依然存在于操作系统)

解决方案:

  1. 将p数组动态分配内存
char* GetMemory()
{
	char* p = (char*)malloc(20);
	strcpy(p, "hello world");
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
	free(str);
	str = NULL;
}

动态分配的内存在堆区,需要使用free才会释放内存空间

  1. 将p数组声明为静态变量
char* GetMemory()
{
	static char p[] = "hello world";//使用static
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

静态变量存放在静态区,生命周期是创建~主函数结束,程序结束才释放其所占内存空间

  1. 将p定义为一个常量字符串
char* GetMemory()
{
	char* p = "hello world";//定义为常量字符串
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

p指针指向了一个常量字符串,常量字符串在程序运行过程期间存储在常量区,GetMemory函数结束后不会被释放,直到程序结束后由系统释放

6. 综合应用

为一维数组分配动态内存

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

int main()
{
	int n = 0;
	scanf("%d", &n);
	int* arr = (int*)malloc(n * sizeof(int));
	if (arr == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < n; i++)
	{
		arr[i] = i;
		printf("%d ", arr[i]);
	}
	free(arr);
	arr = NULL;
	return 0;
}

为二维数组分配动态内存

  1. 运用二级指针来动态分配二维数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int rows = 0;
	int cols = 0;
	scanf("%d%d", &rows, &cols);
	int** arr = (int**)malloc(rows * sizeof(int*));
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		arr[i] = (int*)malloc(cols * sizeof(int));
	}
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = i + j;
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	free(arr);
	arr = NULL;
	return 0;
}
  1. 运用数组指针来动态分配二维数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int rows = 0;
	int cols = 0;
	scanf("%d%d", &rows, &cols);
	int(*p)[cols] = (int (*)[])malloc(rows * sizeof(int[cols]));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			p[i][j] = i + j;
			printf("%d ", p[i][j]);//p[i][j]也可以写成*(*(p+i)+j)
		}
		printf("\n");
	}
	free(p);
	p = NULL;
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值