C语言:动态内存管理

文章目录


一、动态内存函数

要介绍动态内存函数,我们就要知道动态内存函数开辟的空间在内存的什么位置。

如下图所示:

动态内存函数开辟的空间在堆区,该区域的空间,只有free函数和程序结束才会释放。

1. malloc

函数的声明:void* malloc (size_t size);包含在<stdlib.h>头文件中。

调用该函数可以向内存中申请大小为size个字节的连续空间,并返回该空间的起始地址。

注意:

  • 如果malloc开辟空间成功,则返回成功开辟好空间的起始地址
  • 如果malloc开辟空间失败,则返回NULL
  • 不要开辟大小为0的空间,这是标准未定义的。

因此,malloc开辟空间后,我们要检查返回值。

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);//开辟大小为40个字节的空间

	//检查是否空间开辟失败
	if (pa == NULL)
	{
		exit(-1);
	}


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

2. calloc

函数的声明:void* calloc (size_t num, size_t size);包含在<stdlib.h>头文件中。

调用该函数可以向内存中申请num个大小是size个字节的连续空间,再将空间每一个字节初始化为0,并返回该空间的起始地址。

calloc与malloc的唯一区别在于,calloc会将空间每一个字节初始化为0。

如下所示:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* arr1 = (int*)malloc(sizeof(int) * 10);
	if (arr1 == NULL)
	{
		exit(-1);
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	printf("\n");

	int* arr2 = (int*)calloc(sizeof(int), 10);
	if (arr2 == NULL)
	{
		exit(-1);
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}

	free(arr1);
	free(arr2);
	arr1 = NULL;
	arr2 = NULL;
	return 0;
}

3. realloc

函数的声明:void* realloc (void* ptr, size_t size);包含在<stdlib.h>头文件中。

调用该函数可以调整由malloc和calloc开辟的空间大小,调整为size个字节大小的空间。

但要注意,其调整空间大小的方式有两种,原地扩容和异地扩容。

  • 原地扩容,原地址空间后空间大小充足,在原地址空间直接扩容。

  • 异地扩容,原地址空间后空间大小不够,在堆区另找一片充足的空间使用。

  • 如果调整的大小过大,realloc会返回NULL。

c 复制代码
//异地扩容
int main()
{

	int* pa = (int*)malloc(sizeof(int) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}

	printf("%p\n", pa);


	int* tmp = (int*)realloc(pa, sizeof(int) * 20);
	if (tmp != NULL)
	{
		pa = tmp;
		printf("%p\n", pa);
	}
	
	free(pa);
	pa = NULL;
	return 0;
}
c 复制代码
//原地扩容
int main()
{
	char* pa = (char*)malloc(sizeof(char) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}
	printf("%p\n", pa);

	char* tmp = (char*)realloc(pa, sizeof(char) * 20);
	if (tmp != NULL)
	{
		pa = tmp;
		printf("%p\n", pa);
	}

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

4. free

函数的声明:void free (void* ptr);包含在<stdlib.h>头文件中。

调用该函数可以释放由malloc和calloc开辟的空间,指针变量ptr本身内容不变,需要程序员本身置空。

  • 如果ptr是NULL,则函数什么事都不会做
  • 如果ptr并不是malloc和calloc开辟的空间,那free函数的行为未定义

如下:

c 复制代码
int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}

	free(pa);//释放pa所指向的空间
	pa = NULL;//pa置NULL,防止野指针

	return 0;
}

二、常见的错误

1.malloc或calloc开辟的空间未检查

有时开辟空间过大,而未检查时,会发生对NULL的解引用

c 复制代码
int main()
{
	int* pa = (int*)malloc(sizeof(int) * INT_MAX);
	*pa = 10;

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

2.越界访问

访问超出开辟空间大小

c 复制代码
int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}

	for (int i = 0; i < 11; i++)
	{
		pa[i] = 0;
	}

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

3.对非malloc和calloc开辟的空间,用free释放

free函数只能用了释放动态内存空间

c 复制代码
int main()
{
	int arr[10] = { 0 };

	free(arr);
	return 0;
}

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

动态内存空间释放一次即可

c 复制代码
int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}

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

5.用free释放动态内存的一部分

开辟的动态内存空间实际大小要大于我们申请的大小,那多出的一部分空间要记录我们这次申请空间的信息,free函数就可以根据这一部分信息来释放我们申请的空间大小。如果我们传递的指针并未指向空间首地址,那么free就找不到信息来释放空间。

c 复制代码
int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);
	if (pa == NULL)
	{
		exit(-1);
	}

	pa++;
	free(pa);
	pa = NULL;
	return 0;
}

三、通讯录(动态版本改写)

静态版本通讯录

相对于静态版本而言,动态版本改变并不多也不难,下面就是要改变的部分。

  1. 结构
c 复制代码
//动态版本
typedef struct Contact
{
	PeoInfo* data;
	int sz;//记录此时已用的大小
	int capacity;//记录通讯录的大小
}Contact;

2.初始化InitContact

c 复制代码
#define INITSIZE 10

//初始化通讯录
void InitContact(Contact* pc)
{
	pc->sz = 0;
	pc->capacity = INITSIZE;
	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INITSIZE);
	if (pc->data == NULL)
	{
		perror("malloc");
		return;
	}
}
  1. 扩容AddCapacity
c 复制代码
bool AddCapacity(Contact* pc)
{
	PeoInfo* tmp = realloc(pc->data, sizeof(PeoInfo) * (pc->capacity) * 2);
	if (tmp == NULL)
	{
		perror("realloc");
		return false;
	}
	pc->data = tmp;
	pc->capacity *= 2;
	return true;
}

//添加联系人
void AddContact(Contact* pc)
{
	assert(pc);

	if (pc->sz == pc->capacity)
	{
		if (AddCapacity(pc) == true)
		{
			printf("扩容成功\n");
		}
		else
		{
			printf("扩容失败\n");
			return;
		}
	}

	printf("请输入联系人名字:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入联系人性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入联系人年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入联系人电话:>");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入联系人地址:>");
	scanf("%s", pc->data[pc->sz].adder);
	pc->sz += 1;
}
  1. 退出ExitContact
c 复制代码
//退出通讯录
void ExitContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}

总结

以上就是我对于动态内存管理的了解和应用。

相关推荐
MicroTech202511 分钟前
微算法科技(NASDAQ: MLGO)探索Grover量子搜索算法,利用量子叠加和干涉原理,实现在无序数据库中快速定位目标信息的效果。
数据库·科技·算法
今天背单词了吗98036 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
手握风云-1 小时前
优选算法的链脉之韵:链表专题
数据结构·算法·链表
Coding小公仔1 小时前
LeetCode 151. 反转字符串中的单词
开发语言·c++·算法
稳兽龙1 小时前
P1098 [NOIP 2007 提高组] 字符串的展开
c++·算法·模拟
墨小傲1 小时前
基于Linux下的vscode c/c++开发环境搭建详细教程
linux·c语言·vscode
G.E.N.1 小时前
开源!RAG竞技场(2):标准RAG算法
大数据·人工智能·深度学习·神经网络·算法·llm·rag
写个博客1 小时前
暑假算法日记第三天
算法
✿ ༺ ོIT技术༻1 小时前
剑指offer第2版:动态规划+记忆化搜索
算法·动态规划·记忆化搜索
oioihoii2 小时前
C++11标准库算法:深入理解std::none_of
java·c++·算法