动态内存管理

 

声明:由于本人水平有限,文章中难免有不准确和错误之处本人也很想知道这些错误,恳请读者批评指正,大家一起努力,加油!


 本章主要介绍动态内存相关的函数及使用

目录

为什么存在动态内存分配

我们已知的内存开辟方式

动态内存函数的介绍

malloc和free

例子

calloc

例子

realloc 

例子

动态内存中常见的错误

对NULL指针的解引用操作

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

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

使用free释放动态内存中的一部分

对同一块动态开辟的空间多次释放

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

经典的笔试题

笔试1

笔试2

笔试3

笔试4

 柔性数组

使用:


为什么存在动态内存分配

我们已知的内存开辟方式

1.在栈空间上开辟40个字节的空间。

int a = 10;

2.在栈空间上开辟10个字节的连续空间。

char arr[10] = {0};

但上述内存开辟方式有两个缺点:

1.空间的开辟大小是固定的。

2.数组在声明的时候必须指定数组的长度,他所需要的内存在编译时分配。

但有时候我们需要的空间大小在程序运行的时候才能知道,那以上这两种方法就不能满足我们的需求,这时就只能试试动态内存分配了

动态内存开辟

动态内存函数的介绍

malloc和free

作用:这个函数向内存申请一块连续可用的空间,并返回这块空间的指针

void* malloc (size_t size);

例子

#include<stdlib.h>
int main()
{
	//开辟
	int* p = (int*)malloc(10 * sizeof(int));//在堆上开辟10个整形的空间
	//判断
	if (p == NULL)
	{
		perror("main");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;//赋值
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//打印
	}
	//释放
	free(p);
	//p置空
	p = NULL;
	return 0;
}

注意:

1.malloc和ferr一般成对使用,free你把开辟的空间还给操作系统,但free不会把p置为空指针,所以我们要手动把p置空,防止在以后的代码中出现非法访问。

2.free只能释放动态内存开辟的空间。 

开辟失败的例子

	int* p = (int*)malloc(100000000000 * sizeof(int));
	//判断
	if (p == NULL)
	{
		perror("main");
		return 1;
	}

calloc

作用:开辟一块连续的空间,并初始化为0。

void* calloc (size_t num, size_t size);

例子

#include<stdlib.h>
int main()
{
	//开辟
	int* p = (int*)calloc(10, sizeof(int));//在堆区上开辟10个整形的空间
	//判断
	if (p == NULL)
	{
		perror("main");
		return 0;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//calloc会初始化为0
	}
	//释放
	free(p);
	//置空
	p = NULL;
	return 0;
}
注意:malloc和callic的区别就是参数不同,另外calloc会初始化为0,其余全部相同。

realloc 

函数参数

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

realloc如何调整空间?

第一种情况

 如果堆空间后面的空间够用,我们就直接增加40个字节,并把起始地址返回去。

第二种情况

 

如果堆中后面的空间不够用了,realloc会在内存中重新找一块空间,把p所指向空间的数据拷贝下来,然后返回新开辟空间的地址,并ferr掉p所指向的空间。

例子

#include<stdlib.h>
int main()
{
	//开辟
	int* p = (int*)calloc(10, sizeof(int));//在堆区上开辟10个整形的空间
	//判断
	if (p == NULL)
	{
		perror("main");
		return 0;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = 5;
	}
	//需要p指向的空间更大,需要20个整形的空间
	 
	//增容
	int* ptr = (int*)realloc(p,20* sizeof(int));
	if (ptr != NULL)
	{
		p = ptr;//增容成功ptr赋给p
	}

	//释放
	free(p);
	//置空
	p = NULL;
	return 0;
}

总结:malloc,calloc用来开辟空间,ferr释放空间,realloc调整空间。

动态内存中常见的错误

对NULL指针的解引用操作

int main()
{
	int* p = malloc(10000000000);
	*p = 20; //如果p为NULL呢?
	free(p);
	return 0;
}

malloc在堆上开辟内存的时候可能会开辟失败返回NULL,这时候多NULL进行解引用操作肯定会出问题。解决办法就是malloc开辟完后多p指针进行判断。

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

#include<stdio.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return 1;
	}
	int i = 0;
	for (i = 0; i < 40; i++)
	{
		*(p + i) = i;//越界访问,我们只开辟了10个整形的空间
	}
	free(p);
	p = NULL;
}

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

int main()
{
	int arr[10] = { 0 };
	int* p = arr;//arr是在栈上开辟的空间
	free(p);
	p = NULL;
	return 0;
}

使用free释放动态内存中的一部分


int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p++ = i;//改变p的指向
	}
	free(p);//只释放后半部分
	p = NULL;
	return 0;
}

对同一块动态开辟的空间多次释放

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 1;
	}
	//释放
	free(p);
	//。。。。。。
	free(p);

	p = NULL;
	return 0;
}

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

void tast()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return;
	}
	//使用
	//........忘记释放,出了这个函数后再也找不到这块空间了
}
int main()
{
	tast();
	return 0;
}

动态开辟的空间,两种回收方式

1.free主动释放

2.程序结束的时候

经典的笔试题

笔试1

思考如下代码运行的结果:

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	test();
	return 0;
}

分析:

str本是一个指针变量,str传给getmenory是采用值传递的方式,此时的形参p是实参str的一份临时拷贝,函数内部在p这个地址处开辟100字节的空间,但是出了函数后p就会销毁,这时已经内存泄漏了。此时test函数内部的str还是NULL,把hello这个常量字符串拷贝到NULL处,什么也不会发生。

 改正:

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
	if (*p == NULL)
	{
		return;
	}
}
void test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello");
	printf(str);
	free(str);
	str == NULL;
}
int main()
{
	test();
	return 0;
}

笔试2

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

void test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	test();
	return 0;
}

分析:GetMemory函数在返回的是p这个数组的首地址,但是p数组的空间在出函数的时候已经还给了操作系统,printf打印的时候已经算是非法访问了。这种情况问题为返回栈空间的地址。

笔试3

char* GetMemory(char**p,int num)
{
	*p = (char*)malloc(num);
}
void test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	test();
	return 0;

 分析:GetMemory函数内部动态开辟的空间没有free还给操作系统,造成内存泄漏。

笔试4

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;
}

分析:提前释放str所指向的空间,if语句虽然能进去,但是str所指向的空间已经还给了操作系统,再想把world放进去已经造成了非法访问。所以我们free释放后str一定要置空。 

 柔性数组

解释:C99中允许结构的最后一个成员是未知大小的数组,这就叫柔性数组成员

struct S
{
	int a;
	int arr[];
};

柔性数组的特点:

1,结构中柔性数组成员前至少有一个其他成员。

2,sizeof返回时不包含柔性数组的内存。

3,包含柔性数组的结构,用malloc进行动态内存分配,并且分配的大小应大于结构的大小,已适应柔性数组预期的大小。

使用:

struct S
{
	int a;
	int arr[];
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	if (ps == NULL)
	{
		perror("main");
		return 0;
	}
	int i = 0;
	ps->a = 10;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//增容
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
	if (ptr != NULL)
	{
		ps = ptr;
	}
	//释放
	free(ps);
	ps = NULL;
	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、付费专栏及课程。

余额充值