动态内存管理

前言:
本篇博客中所提到的函数需包含头文件 stdlib.h。

一.动态内存分配

在我们写代码时,经常会向内存申请空间,但是那时我们申请的空间是固定的,有时候空间被申请后发现并没有使用这么多的空间。
举个列子:

int main()
{
	int i = 10; //固定的向内存申请4个字节的空间
	int arr[10];//固定的向内存申请40个字节的连续空间
	return 0;
}

上述开辟空间的方式有两个特点
1. 空间开辟大小是固定的
2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

于是c语言就向我们提供了可以实现动态内存开辟的函数,可以让我们自己来决定开辟空间上的大小。

二.动态内存分配函数

在介绍动态内存分配函数前,我们先来简单了解一下内存的划分。动态内存分配函数是在内存中的堆区开辟空间,我们可以将内存简单划分为几个区域。

动态内存

1.malloc和free

1.1malloc函数介绍

void* malloc (size_t size);

malloc 函数会在堆区上向内存申请一块连续可用的空间(size个字节),并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。

  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

  • malloc申请的空间不会进行初始化

1.2free函数的介绍

void free (void* ptr);

在堆区申请的空间使用完之后应该主动释放,free函数便是专门用来释放我们在堆区上申请的空间.

虽然当我们没有主动释放的时候,在程序结束的时候也会被释放,但是如果程序一直运行,那么这块空间将不会被释放,将会造成内存泄漏

  • free函数是专门用来做动态内存的释放和回收的,如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • free函数释放空间之后并不会主动置空。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

1.3malloc函数的使用

#include <stdlib.h>
int main()
{
	//申请40个字节的空间用来存放整型
	//此时返回的是void*的指针,使用时还需要转换
	//void* p = malloc(40);
	
	//在申请空间的同时,转换成我们需要的类型
	int* p = (int*)malloc(40);

	//判断是否申请成功,防止野指针的出现
	if (NULL == p)
	{
		perror("malloc:");
		return 1;
	}

	//存放1—10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放申请的内存
	free(p);
	//置空,防止非法访问
	p = NULL;
	return 0;
}

mallo函数

  • 注意点

在代码的最后我们对申请空间进行了置空的操作,为什么要进行置空的操作?

在调试窗口输入p,(逗号)10,就可以以p指针的视角向后观察10个元素
free函数

此时我们已经将申请的空间进行了释放,我们发现此时p依然存放着那块空间的起始地址,如果不对p进行置空,下次有人再去使用p的时候就会造成非法访问。

free函数

2.calloc

2.1calloc函数的介绍

void* calloc (size_t num, size_t size);

calloc 函数也用来用来进行动态内存分配的,它和malloc函数的区别如下:

  • 函数 realloc和malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

相同点:

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,也需要进行判断。

2.2calloc函数的使用

int main()
{
	int* p = (int*)calloc(10, sizeof(int));

	if (NULL == p)
	{
		perror("calloc:");
		return 1;
	}

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

	free(p);

	p = NULL;
	return 0;
}

calloc函数

关于malloc和calloc在效率上的区别:

  • malloc的效率比calloc高,因为malloc申请空间的时候并不会对空间进行初始化。
  • calloc的效率比malloc低,因为calloc在申请空间的同时进行了初始化。

3.relloc

3.1.relloc函数的介绍

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

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

ptr指向由前面malloc,calloc或者realloc开辟的空间,size是调整之后的空间大小。

注意点:

  • ptr 是要调整的内存地址
  • 如果传的是空指针,realloc和malloc相同,开辟一块空间,返回空间的起始地址。
  • size 是调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • realoc函数扩容失败会返回NULL;

3.2realloc函数的工作原理

realoc函数

3.3realloc函数的使用

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));

	if (NULL == p)
	{
		perror("malloc:");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = 1;
	}

	//空间不够,增加5个整型的空间
	//若用p接收时扩容失败,将丢失p指向空间的数据
	int* ptr = (int*)realloc(p, 10 * sizeof(int));

	if (NULL == ptr)
	{
		perror("realoc:");
		return 1;
	}
	
	p = ptr;
	ptr = NULL;

	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;
	return 0;
}

reallo函数

三.常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));

	//未判断是否为空,就直接使用
	//if (NULL == p)
	//{
	//	perror("malloc:");
	//	return 1;
	//}
	
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		//如果p是空指针,就会有问题
		p[i] = 1;
	}

	free(p);
	p = NULL;
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));

	if (NULL == p)
	{
		perror("malloc:");
		return 1;
	} 

	int i = 0;
	//越界访问
	for (i = 0; i < 10; i++)
	{
		p[i] = 1;
	}

	free(p);
	p = NULL;
	return 0;
}

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

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

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

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

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

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 //p = NULL; //(及时置空,即使再次释放也不会出现问题)
 free(p);//重复释放,出现问题
}

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

void test()
{
	int *p = (int *)malloc(100);
 	if(NULL != p)
 	{
 		*p = 20;
 	}
}
int main()
{
 	test();
 	//此时忘记释放空间
 	//此时想释放空间也不行(没有传回来空间的起始地址)
	return 0;
}

解决方法:

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

    //使用完之后进行释放
    free(p);
    p = NULL;
}
int main()
{
    test();
    return 0;
}

注意:
若需要返回空间,应该写好注释,记得释放

注释不仅能提醒我们自己。也可以提醒使用代码的其他人。

//函数内部malloc申请空间,返回空间的起始地址,记得释放
int* test()
{
    int* p = (int*)malloc(100);
    if (NULL != p)
    {
        *p = 20;
    }
    
    return p;
}
int main()
{
    int* ret = test();
    free(ret);
    ret = NULL;
    return 0;
}

四.经典的笔试题

4.1内存泄漏and非法访问

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

void Test(void) 
{
	char* str = NULL;
	GetMemory(str); //传值调用
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}

存在的问题:

  • 当str传给p的时候,p是str的一份临时拷贝,有自己独立的空间,当GetMemory函数内部申请了空间后,地址放在p中,此时str依旧是空指针,当GetMemor函数结束后,未返回申请空间的首地址,同时销毁变量p,此时已经无法在对申请的空间进行释放,造成内存泄漏。

  • strcpy拷贝的时候,传的是空指针,造成非法访问。

解决方法:

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

void Test(void)
{
	char* str = NULL;
	GetMemory(&str); //传址调用

	if (NULL == str)//判断是否为空
	{
		perror("GetMemory:");
		return;
	}

	strcpy(str, "hello world");
	printf(str);

	free(str); //释放
	str = NULL; //置空
}
int main()
{
	Test();
	return 0;
}

4.2返回栈空间地址的问题

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

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

int main()
{
	Test();
	return 0;
}

存在的问题:

  • 在GetMemory函数内创建字符串数组,返回字符串数组的起始地址,虽然起始地址被返回,但是申请存放字符串数组的空间在GetMemory函数结束时已经还给操作系统,当我们在利用返回的地址打印时(随机值),形成非法访问。

解决方法:

char* GetMemory(void) 
{
	//1.static修饰
	//static char p[] = "hello world";

	//2.常量字符串
	char* p = "hello world"; 
	return p;
}

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

int main()
{
	Test();
	return 0;
}

注意点:

  • 应该避免返回局部变量的地址。
  • 需要返回局部变量的地址时,可以使用static修饰局部变量。

4.3非法访问

void Test(void) 
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello"); 

	free(str);

	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}

存在的问题:

  • 申请空间后并未判断,若申请失败返回NULL,在使用strcpy函数,将造成非法访问。
  • 释放空间后,申请的空间还给操作系统,并未及时置空,此时str中依旧存放着起始地址,但是str已经变成了野指针。
  • 对野指针进行strcpy函数的拷贝,造成非法访问。

解决方法:

void Test(void) 
{
	char* str = (char*)malloc(100);
	if (NULL != str)
	{
		strcpy(str, "hello");
	}
	free(str);
	str = NULL;

	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清隆綾小路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值