C语言-动态内存管理

目录

前言

一、什么是动态内存分配

二、动态内存函数介绍

1.malloc 和 free

2.calloc

3.realloc

三、常见的动态内存错误

1.对NULL的解引用

2.对开辟的空间越界访问

3.对非动态开辟的内存使用free()

4.使用free()释放一块动态开辟的内存空间中的一部分

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

6.动态开辟内存忘记释放

四、C语言实现通讯录(动态开辟内存空间)

contact.h

contact.c

test.c

总结



前言

一、什么是动态内存分配

        内存的大致布局:

        我们已经掌握的开辟内存的方式有:

//在栈空间上开辟4个字节的内存空间
int value = 10;

//在栈空间上开辟10个字节的连续空间
char arr[10] = { 0 };

这样的开辟空间的方式有两个特点:

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

2.数组在声明的时候必须开辟足够大的空间以备使用

但是有时候我们需要开辟的空间的大小只有在程序运行的时候才能知道,那么上述开辟空间的方式就无法满足我们的需求了,这个时候就需要用多少空间就开辟多少空间了


二、动态内存函数介绍

1.malloc 和 free

#include <stdio.h>
#include <stdlib.h> //malloc、free需要的头文件

int main()
{
	//尽管malloc的返回值是void*类型的
	//但是void*类型的使用起来不方便,例如无法直接进行解引用操作
    //参数size为申请的空间的大小,单位是字节
	//void* p = malloc(12);
	//因此我们使用强制类型转换,需要什么就声明什么类型的变量
	int* pi = (int*)malloc(4);
	char* pc = (char*)malloc(sizeof(char));
	int* pa = (int*)malloc(10 * sizeof(int));
	return 0;
}

那么是否有可能开辟内存失败呢?

总结:

1.malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

2.如果开辟空间失败,则返回空指针NULL

3.因为malloc的返回值为void*类型,我们在使用的时候需要自己决定类型

4.如果参数size为0,malloc的行为是标志未定义的,具体取决于编译器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main()
{
	//开辟动态内存
	int* p = (int*)malloc(20);
	//检验是否开辟成功
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		printf("申请内存成功\n");
	}
	//使用动态内存
	//...
	//释放
	free(p);//free的头文件是<stdlib.h>
	return 0;
}

注意:

1.free函数是用来释放开辟的动态内存的,参数为需要释放的指向动态内存的指针

2.使用完开辟的动态内存要记得释放,不要随意调整动态内存开辟的初始指针防止无法释放

3.如果参数ptr指向的空间不是动态开辟的,free的行为也是标志未定义的

4.如果参数ptr是空指针NULL,free函数什么都不会做

2.calloc

功能:为num个大小为size的元素开辟一块内存空间,并且把空间每个字节都初始化为0

注意:calloc和malloc都在堆区是申请内存,如果需要初始化可以使用calloc,不需要初始化可以使用malloc

3.realloc

        有时候我们会发现先前申请的内存空间太小或者太大了,希望能够对它进行一些调整,就需要用到realloc函数了

功能:把需要调整的内存地址ptr重新分配内存空间,新的内存空间大小为size(单位:字节),返回值为调整之后的内存的起始位置

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	//这样可以吗
	//p = (int*)realloc(p, 40);
	int *ptr = (int*)realloc(p, 40);
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		printf("relloc: %s\n", strerror(errno));
		return 1;
	}
	for (int i = 5; i < 10; i++)
	{
		p[i] = i + 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

        实际上,realloc在调整内存空间的时候存在两种情况:

我们可以把realloc的大概行为列举出来:

1.判断原有空间之后是否有足够大的空间

情况1:

2.在原来的空间的基础上进行扩容

3.返回原来空间的地址

情况2:

2.寻找更大的能够满足需求的空间

3.将原来的数据拷贝到新的空间,并且释放原来的空间

4.返回新空间的地址


三、常见的动态内存错误

1.对NULL的解引用

void test()
{
	int* p = (int*)malloc(4);
	*p = 20;//如果p值为空指针就会产生问题
	free(p);
	p = NULL;
}

2.对开辟的空间越界访问

void test()
{
	int* p = (int*)malloc(10*sizeof(int));
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		exit;
	}
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		p[i] = i + 1;//i=10的时候越界访问
	}
	free(p);
	p = NULL;
}

3.对非动态开辟的内存使用free()

void test()
{
	int i = 10;
	int* p = &i;
	free(p);//p不是动态开辟的内存空间
	p = NULL;
}

4.使用free()释放一块动态开辟的内存空间中的一部分

void test()
{
	int* p = (int*)malloc(10*sizeof(int));
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		exit;
	}
	p++;
	free(p);//p不再指向动态内存的起始位置
	p = NULL;
}

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

void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		exit;
	}
	free(p);
	free(p);//重复释放
	p = NULL;
}

但是如果在释放动态内存空间后将指针置空,即使重复释放也不会产生问题
void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		exit;
	}
	free(p);
    p = NULL;
	free(p);//p为空指针,free函数不做任何操作
	p = NULL;
}

6.动态开辟内存忘记释放

void test()
{
	int* p = (int*)malloc(sizeof(int));
	if (NULL != p)
	{
		*p = 10;
	}
}
//每次调用test开辟的动态内存空间都没有释放

int main()
{
	while (1)
	{
		test();//死循环地调用函数test
	}
	return 0;
}


四、C语言实现通讯录(动态开辟内存空间)

contact.h

#pragma once

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define MAXSIZE 1000
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 15
#define MAX_ADDR 30
#define DEFAULT_SZ 5
#define INT_SZ 3

typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;

//静态版本的设计
//typedef struct Contact
//{
//	PeoInfo data[MAXSIZE];//存放数据
//	int sz;//记录通讯录中的有效信息个数
//}Contact, * pContact;

//动态版本的设计
//默认能够存放5个人的信息
//如果不够,每次增加3个信息的空间
typedef struct Contact
{
	PeoInfo *data;//data指向了存放数据的空间
	int sz;//记录通讯录中的有效信息个数
	int capacity;//记录通讯录当前的容量
}Contact, * pContact;

//初始化通讯录
void InitContact(Contact* pc);

//增加指定联系人
void AddContact(Contact* pc);

//显示联系人信息
void ShowContact(const Contact* pc);

//删除指定联系人
void DelContact(pContact pc);

//查找指定联系人
void SearchContact(const Contact* pc);

//修改通讯录
void ModifyContact(Contact* pc);

//排序通讯录元素
void SortContact(Contact* pc);

//清空所有联系人
void DestroyContact(Contact* pc);

contact.c


#include "contact.h"

//静态版本的设计
//void InitContact(Contact* pc)
//{
//	pc->sz = 0;
//	memset(pc->data, 0, sizeof(pc->data));
//}

//动态版本的设计
void InitContact(Contact* pc)
{
	pc->sz = 0;
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
}

//静态版本的设计
//void AddContact(Contact* pc)
//{
//	if (pc->sz == MAXSIZE)
//	{
//		printf("通讯录已满,无法增加\n");
//		return;
//	}
//	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");
//}

//扩容失败返回0;扩容成功或者不需要扩容返回1
int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INT_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity:%s\n", strerror(errno));
			return 0;
		}
		pc->data = ptr;
		pc->capacity += INT_SZ;
		printf("增容成功,当前容量:%d\n", pc->capacity);
		return 1;
	}
	return 1;
}

//动态版本的设计
void AddContact(Contact* pc)
{
	if (0 == CheckCapacity(pc))
	{
		printf("空间不够,扩容失败\n");
		return;
	}
	else
	{
		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");
	}
}

void ShowContact(const Contact* pc)
{
	int i = 0;
	//姓名      年龄      性别     电话      地址
	//zhangsan 20        男      123456    北京
	//
	//打印标题
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	//打印数据
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s %-4d %-5s %-12s %-30s\n",
			pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

static int FindByName(const Contact* pc, char name[])
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1;
}

void DelContact(pContact pc)
{
	char name[MAX_NAME] = { 0 };
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	//删除
	//1. 找到要删除的人 - 位置(下标)
	printf("输入要删除人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	int i = 0;
	//2. 删除 - 删除pos位置上的数据
	for (i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功\n");
}

void SearchContact(const Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	//查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//打印
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	//打印数据
	printf("%-10s %-4d %-5s %-12s %-30s\n",
		pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

void ModifyContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	//修改
	printf("请输入名字:>");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pos].age));
	printf("请输入性别:>");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址:>");
	scanf("%s", pc->data[pos].addr);

	printf("修改成功\n");
}

//按照名字来排序
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

void SortContact(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
	printf("排序成功\n");
}

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
	printf("释放内存\n");
}

test.c


#include "contact.h"

void menu()
{
	printf("|********************************|\n");
	printf("|****   0. exit    1. add    ****|\n");
	printf("|****   2. del     3. search ****|\n");
	printf("|****   4. modify  5. show   ****|\n");
	printf("|****   6. sort    7. clean  ****|\n");
	printf("|********************************|\n");
}

enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
	CLEAN
};

int main()
{
	int input = 0;
	//构造一个通讯录
	Contact con;
	//初始化通讯录
	InitContact(&con);
	do
	{
		menu();
		printf("请选择 :>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		case CLEAN:
			DestroyContact(&con);
			//ShowContact(pc);
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}


总结

猜你喜欢

转载自blog.csdn.net/weixin_72501602/article/details/129641158