动态内存管理

前言

        为什么存在动态内存的分配呢?我们可以思考我们写过的代码,当我们创建数组时,创建了很多个空间,但是有些空间特别长时间都不会用到,所以这一现象就造成了空间的浪费。因此我们就引出了动态内存分配这个概念。

动态内存函数的介绍

一、malloc

1、函数malloc()

void* malloc (size_t size);

函数说明:这个函数向内存申请了size个字节的连续空间,并返回指向这块空间的指针。

2、函数介绍

<1>如果函数开辟空间成功,那么函数返回指向该空间的指针。

<2>如果函数开辟空间失败,那么函数返回NULL。(因此一定要检查返回的是不是空指针)

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

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

二、free

1、函数free()

void free(void* ptr);

函数说明:这个函数的功能是将动态内存进行释放。

2、函数介绍

<1>如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的。

<2>如果参数ptr为空指针,那么函数什么也不用做。

<3>与动态内存开辟函数是成对出现的。

代码:

#include <stdio.h>
int main()
{
	int num = 0;
	scanf("%d", &num);
	int* ptr = NULL;
	ptr = (int*)malloc(num*sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i<num; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;
	return 0;
}

三、calloc

1、函数calloc()

void* calloc(size_t num,size_t size);

函数说明:此函数也用于动态内存开辟,返回值是指向所开辟空间的指针。

2、函数介绍

<1>函数的功能是为num个大小为size的元素开辟一块空间,并把空间的每个字节初始化为0

<2>与函数malloc相比,malloc不会将所开辟的空间初始化为0,而calloc会将所开辟的空间初始化为0.

代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int *p = calloc(10, sizeof(int));
	if (NULL != p)
	{
		for (int i = 0; i < 10; i++)
			printf("%d ",*(p+i));
	}
	free(p);
	p = NULL;
	return 0;
}

四、realloc

1、函数realloc()

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

函数说明:有时申请的空间太大,有时申请的空间太小,所以为了灵活的改变所申请的空间就需要realloc函数。

2、函数介绍

<1>ptr是要调整的内存地址

<2>size是调整之后新大小

<3>返回值为调整之后的内存起始位置。

<4>realloc在调整内存空间时存在三种情况。

①在原内存空间的基础上往后继续增加空间,返回值为原本开辟的空间的指针。(红色空间为原内存空间,需要增加6个字节的空间)

 ②原内存空间后面没有充足的空间可以增加时,realloc会在后面找个位置开辟新空间,并且将原内存空间的值拷贝到新开辟的内存空间,同时释放原内存空间,返回值为指向新开辟的内存空间的指针。(红色空间为原内存空间,需要增加6个字节的空间)

 ③内存中没有充足的空间可以增加需要的空间时,原内存空间大小维持不变,返回值为NULL。(红色空间为原内存空间,需要增加6个字节的空间)

<5>如果realloc的第一个参数为空指针,那么可以起到和malloc一样的效果。

  代码:

int main()
{
	int *ptr = malloc(100);
	if (ptr != NULL)
	{
		//
	}
	else
	{
		return;
	}
	//ptr = realloc(ptr, 1000);//直接将返回值放到ptr中是存在缺陷的,因为如果realloc返回NULL,那么原本的数据也将丢失。
	int*p = NULL;
	p = realloc(ptr, 1000);
	if (p != NULL)
	{
		ptr = p;
	}
	free(ptr);
	return 0;
}

常见的动态内存错误

1、对NULL指针的解引用操作

如:如果需要动态开辟的内存太大,而内存空间不足,就会返回NULL造成错误。

​
void test()
{
	int* p = (int*)malloc(INT_MAX);
	*p = 20;
	free(p);
}

​

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

如:

void test()
{
	int i = 0;
	int *p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		return;
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i=10时,越界访问了
	}
	free(p);
}

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

如:

void test()
{
	int a = 10;
	int *p = &a;
	free(p);
}

4、使用free释放动态内存只释放掉一部分,造成内存泄漏。

如:

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//因为p不再指向内存的起始位置,所以释放p时,有一部分空间未被释放
}

5、对同一块动态内存多次释放。

如:

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);
}

6、动态开辟内存忘记释放。(会造成内存泄漏)

如:动态内存的释放有两种方式①手动释放②程序结束

void test()
{
	int* p = (int*)malloc(100);
	if (NULL! = p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);
	return 0;
}

经典例题分析

1、

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

分析:将NULL传到函数GetMemory建立的局部变量p中,然后,给p动态分配空间,然后GetMemory函数结束,局部变量p销毁,str中仍是NULL,且给p动态分配的空间还为销毁,所以将“hello world”拷贝到NULL中是错误的。

总结:此程序有两个错误,其一动态分配的空间未释放,造成了内存泄漏。其二将字符串拷贝到空指针指向的空间,造成错误。

代码修改:

<1>

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

<2>

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

2、

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

分析:当程序执行到进入GetMemory函数时,在函数内部创建了数组,然后将数组首元素的地址返回放到了str中,但是,当GetMemory函数执行完毕后,函数内部创建的数组销毁了,因此返回的地址没有任何的意义。

3、

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

分析:此程序仅有一个问题,也是最为致命的问题,就是开辟了动态内存,用完之后没有释放。

4、

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

分析:此代码使用完动态内存之后释放了,但是并未将str置成空指针,因此,还会继续往下执行程序,下面的程序就是将一个字符串拷贝到NULL指向的空间,因此造成了错误。

柔性数组

 1、柔性数组:C99中,结构中的最后一个元素允许是未知大小的数组,这个数组就叫做柔性数组。

2、柔性数组的表示:

<1>

typedef struct st_type
{
	int i;
	int a[0];
}type_a;

<2>

typedef struct st_type
{
	int i;
	int a[];
}type_a;

有的编译器支持第一种,有的编译器支持第二种,有的两者都可以。

3、柔性数组的特点:

<1>结构中的柔性数组成员前面必须至少有一个其他成员。

<2>sizeof求这种结构体的大小时,返回的大小不包括柔性数组的大小。

<3>包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

对<2>进行解释的代码:

typedef struct st_type
{
	int i;
	int a[0];
}type_a;
int main()
{
	printf("%d\n",sizeof(type_a));//结果为4
	return 0;
}

4、柔性数组使用的代码:

#include<stdio.h>
typedef struct st_type
{
	int i;
	int a[0];
}type_a;
int main()
{
	type_a* p = (type_a*)malloc(sizeof(type_a)+10*sizeof(int));
	p->i = 1;
	for (int j = 0; j < 10; j++)
		scanf("%d",&p->a[j]);
	for (int k = 0; k < 10; k++)
		printf("%d ",p->a[k]);
	return 0;
}

5、柔性数组的优势

柔性数组的使用中介绍的代码也可以设计为如下形式

#include<stdio.h>
typedef struct st_type
{
	int i;
	int* a;
}type_a;
int main()
{
	//将结构体建立在栈区
	/*type_a p;
	p.i = 1;
	p.a = (int*)malloc(10 * sizeof(int));
	for (int j = 0; j < 10; j++)
		scanf("%d", &p.a[j]);
	for (int k = 0; k < 10; k++)
		printf("%d ", p.a[k]);*/
	//将结构体建立在堆区
	type_a* p = (type_a*)malloc(sizeof(type_a));
	p->i = 1;
	p->a = (int*)malloc(10 * sizeof(int));
	for (int j = 0; j < 10; j++)
		scanf("%d",&p->a[j]);
	for (int k = 0; k < 10; k++)
		printf("%d ",p->a[k]);
	return 0;
}

但是两个代码相比较,柔性数组的使用有明显的两个好处。

<1>方便内存释放。

    如果我们的代码是在一个给别人的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

<2>这样有利于访问速度。

连续的内存有益于提高访问速度,也有益于减少内存碎片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不会算法的笨小孩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值