C语言动态内存管理

        上期我们手搓出了一个通讯录,当时是创建了一个名为context的结构体,里面又有装着人的信息的数组peo_info,我们创建了100个这个类型的数组,因此内存是非常大的,如果我们只在里面存了5~6个人的信息那岂不是大材小用??所以此时就有一个思路,我们把初始时候的大小设为5个,满了就+2个,以此类推,那么空间就不会被浪费太多对吧,所以C语言中动态内存的知识就来咯~

一、动态内存函数介绍

        1.1 mallocfree

        C语言提供了动态内存开辟的函数malloc

void* malloc(size_t size)//动态内存的开辟

        这个函数会向内存申请一个 连续可用的空间,并返回空间的起始地址(指针)

        另外还需要注意以下4点:

        1. 如果开辟空间成功,则返回空间的指针

        2. 如果开辟空间失败,则返回空指针,因此开辟完空间的时候需要我们去检查一下这个指针是否为空

        3. 返回类型为void*,因为他并不知道我们要开辟的这块空间的类型是什么样的,所以我们在接收指针的时候需要强制类型转换一下

        4. 当size = 0的时候,纯纯犯贱,此时连系统都看不下去懒得救你

         来举个例子,假设我们需要创建大小为num的int型数组,我们可以这样写:

	int num = 0;
	scanf("%d", &num);
	int arr[num] = { 0 };

        但是这样写在VS的环境下是不行滴,所以我们就需要改为动态内存

	int num = 0;
	scanf("%d", &num);
	int* ptr = (int*)malloc(sizeof(int) * num);//动态内存开辟
	if (ptr == NULL)//检测是否开辟成功
	{
		perror("malloc:");
		return 1;
	}

        之后就可以进行对应的操作啦,当你操作完了之后,要记得向内存还回去这个空间,不要有借无还噢,因此我们就需要用到空间释放的函数free

        C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的

void free (void* ptr);//空间的释放

        需要注意的是如果ptr为空指针那么函数什么都不干,空间释放完了之后他不会把ptr置为空指针,因此为了防止ptr成为野指针,需要我们自己将ptr置为空指针

	int num = 0;
	scanf("%d", &num);
	int* ptr = (int*)malloc(sizeof(int) * num);//动态内存开辟
	if (ptr == NULL)//检测是否开辟成功
	{
		perror("malloc:");
		return 1;
	}
	//操作
	//...
	//释放
	free(ptr);
	ptr = NULL;

        这样才正确使用了动态内存的开辟和释放

        1.2 calloc

        calloc和malloc类似,都是动态内存开辟的函数

void* calloc (size_t num, size_t size);

        不一样的是calloc需要我们输入两个参数,第一个是元素的个数,第二个是元素的大小,开辟之后的空间它会全部帮我们初始化为0

int main()
{
	int num = 0;
	scanf("%d", &num);
	int* ptr = (int*)calloc(num, sizeof(int));//动态内存开辟
	if (ptr == NULL)//检测是否开辟成功
	{
		perror("calloc:");
		return 1;
	}
	//操作
	//...
	//释放
	free(ptr);
	ptr = NULL;
	return 0;
}

        malloc是不会帮你初始化这块空间的哟,所以我个人认为calloc更方便一些

        1.3 realloc

        动态内存的动来了,realloc函数的出现让动态内存管理更加灵活,它可以使我们申请的空间大小变为我们想要的大小

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

        显然第一个参数是开辟空间的指针,第二个是改变后的空间大小(字节)

        需要注意的是当他改变空间成功之后还是返回这个空间的指针,但是如果没有开辟成功的话返回的是一个空指针(NULL),因此我们应该先检测一下返回的指针是否为空指针,然后再造作,以防万一找不到之前开辟的空间

         realloc调整空间有两种方式;

        1.  如果后面有足够多的连续空间,那么可以直接将需要增加的空间放后面(连续)

        2. 如果后面连续的空间不够,函数会在堆区找到合适大小的空间,返回新地点的指针

int main()
{
	int* ptr = malloc(100);
	if (ptr == NULL)
	{
		perror("malloc:");
		return 1;
	}
	//操作
	//……

	//增加空间
	int* p = realloc(ptr, 200);
	if (p)//先检测p是否为空指针,以防空间丢失
	{
		ptr = p;
	}
	else
	{
		perror("realloc:");
		return 1;
	}
	free(ptr);//释放空间
	return 0;
}

二、通讯录2.0版(动态内存版)

        有了上述的知识之后,我们就可以将我们的通讯录弄得更为完善

        2.1 结构体context的修改

        这是我们上一个静态版本的context结构体

//静态版本
typedef struct context
{
	peo_info data[MAX];//存放人的信息
	int sz;//存放当前存的人的个数
}context;

        我们用的是一个100大小的数组来控制我们的通讯录的大小,我们要将它改为动态内存版的就不需要数组了,只需要一个指针(指向开辟好的内存空间)就行,然后我们还需要一个值来纪律当前的内存大小是多少,方便之后增加内存

//动态版本
typedef struct context
{
	peo_info* data;//存放人的信息
	int sz;//存放当前存的人的个数
	int capacity;//当前通讯录的最大容量
}context;

        2.2 初始化函数的修改

        这是我们上个版本的初始化函数

//初始化通讯录
void init_context(context* pc)
{
    assert(pc);
	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
}

        现在我们要先申请5个context字节大小的空间,并初始化,所以直接用calloc函数,申请并初始化空间,再把当前申请好的空间初始化为5

void init_context(context* pc)
{
	assert(pc);
	pc->sz = 0;
	peo_info* ptr = (peo_info*)calloc(DEFAULE_SZ,sizeof(peo_info));
	if (ptr == NULL)
	{
		perror("init_context::calloc");
		return;
	}
	pc->data = ptr;
	pc->capacity = DEFAULE_SZ;
}

        2.3 增加空间

        每次增加联系人的时候都检查一下空间满了没有,如果满了,就像内存再申请2个空间

//动态版本
void check_capacity(context* pc)
{
	assert(pc);
	if (pc->sz == pc->capacity)
	{
		//增加容量
		peo_info* ptr = realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(peo_info));
		if (NULL == ptr)
		{
			perror("check_capacity::realloc");
			return;
		}
		pc->data = ptr;
		pc->capacity += INC_SZ;
		printf("增加容量成功\n");
	}
}

void add_context(context* pc)
{
	assert(pc);
	check_capacity(pc);
	//增加一个人的信息
	printf("请输入名字:");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入年龄:");
	scanf("%d", &pc->data[pc->sz].age);
	printf("请输入性别:");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话:");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入地址:");
	scanf("%s", pc->data[pc->sz].addr);
	pc->sz++;
	printf("添加成功\n");
}

        这样我们就能得到一个内存空间灵活的通讯录

三、动态内存常见的错误

        3.1 对NULL解引用

int main()
{
	int* ptr = malloc(100);
	*ptr = 20;//如果没有开辟成功,ptr就为空指针
	free(ptr);
	return 0;
}

        因此我们需要先检查malloc是否返回的是空指针

int main()
{
	int* ptr = malloc(100);
	if (ptr == NULL)
	{
		perror("malloc:");
		return 1;
	}
	*ptr = 20;//如果没有开辟成功,ptr就为空指针
	free(ptr);
	return 0;
}

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

int main()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
	return 0;
}

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

int main()
{
	int a = 10;
	int* p = &a;
	free(p);//这样可以吗?
	return 0;
}

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

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

        3.5 重复释放

int main()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//多次释放
}

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

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

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值