C语言——动态内存管理

一、动态内存管理引入

1、为什么要动态内存管理

静态分配的内存大小在编译时就固定了,不论是否使用都会占用这部分资源。动态内存管理可以根据实际需要来分配内存,这有助于避免浪费和更有效地利用有限的内存资源。

许多复杂的数据结构,如树、图和各种链式结构,通常需要在运行时动态地创建和销毁节点,这也需要动态内存管理。

二、相关函数

C语言的动态内存管理涉及几个关键函数,这些函数通常包含在C标准库的stdlib.h头文件中。动态内存管理允许程序在运行时分配和释放内存,这对于处理大小不确定或数据量大的情况非常有用。以下是C语言中动态内存管理的几个关键函数:

1、malloc

1)函数作用

堆区(heap)分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。通常用于分配单一大块内存,例如数组或结构体。

2)函数原型:

3)函数参数:

size是需要初始化内存块的大小,单位是字节。

如果size是0,这时malloc的行为是标准未定义的,取决于编译器。

4)返回值:

如果分配成功,返回指向这块内存的指针。如果分配失败,比如内存不足,返回NULL指针。

malloc的返回值是void*类型的,是因为函数并不知道开辟空间的用途,所以使用泛型指针。

5)示例:

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

int main()
{
	int n = 0;

	printf("请输入数组大小>:");
	scanf("%d", &n);

	int* arr = (int*)malloc(n * sizeof(int));
	if (arr == NULL)
	{
		fprintf(stderr, "内存分配失败\n");
		return EXIT_FAILURE;//内存分配失败,主函数以失败状态退出
	}
	
	//分配成功后的操作
	int i = 0;
	printf("请依次输入数组元素>:");
	for (i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	free(arr);
    arr = NULL;
	return 0;
}

运行结果:

虽然当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议您在不需要内存时,都应该调用函数 free() 来释放内存。

在释放内存后,将指针置为NULL。

2、free

1)函数作用:

用于释放不再需要的动态分配的内存,是防止内存泄漏的关键步骤。释放之前通过malloccallocrealloc分配的内存块。free后的指针应该设置为NULL,避免悬挂指针(即指针指向的内存已被释放或不可用)。

2)函数原型:

3)函数参数:

如果memblock指针指向的空间不是动态内存开辟的,则free的行为是未定义的。

如果memblock是空指针(NULL),则free函数什么也不做。

4)示例:

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

int main()
{
	int* p = 0;
	p = (int*)malloc(sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));//开辟失败
		return EXIT_FAILURE;
	}

	//开辟完成后的操作
	*p = 6;
	printf("%d\n", *p);

	free(p);//释放开辟的内存块
	p = NULL;//将指针置空
	return 0;
}

3、calloc

1)函数作用:

contiguous allocation,意思是连续分配。calloc 函数分配多个连续的内存块,每块的大小相同,并且将所有位自动初始化为零。

也就是在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。

通常用于分配一定数量相同类型的对象,并需要其初始化为0的情况。

2)函数原型:

3)函数参数:

它接受两个参数,即需要分配的内存块数 num 和每个内存块的大小 size (以字节为单位),也就是分配num个对象的内存,每个对象的大小都是size字节。

4)函数返回值:

如果分配成功,返回指向这块内存的指针,如果分配失败,返回NULL

5)示例:

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

int main()
{
	int* arr = (int*)calloc(10,sizeof(int));
	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));//开辟失败
		return EXIT_FAILURE;
	}

	//开辟完成后的操作
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	free(arr);//释放开辟的内存块
	arr = NULL;//将指针置空
	return 0;
}

运行结果:

可以发现,calloc 函数开辟的空间都被初始化为0。

4、realloc

1)函数作用:

尝试重新调整先前通过malloccallocrealloc函数分配的内存块的大小。

2)函数原型:

3)函数参数:

i.参数具体情况:

它接受两个参数,即一个先前分配的内存块的指针 memblock 和一个新的内存大小 size ,然后尝试重新调整先前分配的内存块的大小到 size 大小。也就是说:memblock 是需要调整的内存块的指针,size 是调整后的新大小。

ii.realloc 的具体实现:

如果新大小比原来小,realloc 可能会释放多余的内存。

如果新大小比原来大,realloc 会尝试扩展现有的内存区域。如果不能扩展,它将分配一块新的内存区域,将原数据复制过去,然后释放原内存区域。

也就是说在新空间比原来空间大的情况下,realloc 的行为有两种情况:

第一种,现有的内存区域后面又充足的空间用来扩展:

第二种,现有的内存区域后面没有充足的空间用来扩展:

iii.参数的特殊情况:

如果 memblock 指针为 NULL ,则 realloc 的行为与 malloc 的行为相同,开辟一个大小为 size 字节的内存块,然后返回这个内存块的指针。

如果size为0,且 memblock 不为NULL,则 memblock 指向的内存块可能会被释放,并返回NULL

对于 realloc 这个函数,在开辟“新”空间时,还会把原来的数据复制到新空间中,所以会出现以下问题:如果新块比原来的小,数据可能会丢失;如果新块比原来的大,新部分不会被初始化。

4)函数返回值:

如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。

5)注意事项:

在使用 realloc 函数时,realloc 会返回扩展后的内存块的指针,这里的返回值不能用原来的指针变量来接收,因为,realloc 如果调整失败,则可能返回 NULL ,如果复制给原来的指针变量,就会导致原来内存块的地址丢失,导致内存泄漏,因为 realloc 调整失败不会释放原来的内存块。

为了避免这个问题,你应该将realloc的返回值赋给一个临时指针变量,然后检查这个临时变量是否为NULL。只有在它不为NULL的情况下,你才更新原来的指针变量。

正确使用示例:

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

int main()
{
    // 简单示例,分配一个大小为10的int数组
    int* ptr = malloc(10 * sizeof(int));
    if (ptr == NULL)
    {
        // 处理分配失败
        printf("%s\n",strerror(errno));
        return -1;
    }

    // ... 使用ptr

    // 重新分配内存,尝试扩展到20个int
    int* temp = realloc(ptr, 20 * sizeof(int));
    if (temp == NULL)
    {
        // 处理重新分配失败
        printf("%s\n",strerror(errno));
        free(ptr);
        ptr = NULL;
        return -1;
    }
    else
    {
        // 重新分配成功,更新原来的指针
        ptr = temp;
    }

    // ... 使用新的ptr

    // 最终释放内存
    free(ptr);
    // 指针置空
    ptr = NULL;

    return 0;
}

6)示例:

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

int main()
{
	int* arr = (int*)malloc(10 * sizeof(int));
	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));//开辟失败
		return EXIT_FAILURE;
	}

	//开辟完成后的操作
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i + 1;
	}

	int* temp = (int*)realloc(arr, 20 * sizeof(int));//扩容

	if (temp == NULL)//调整失败
	{
		printf("%s\n", strerror(errno));//抛出错误信息
		free(arr);//释放先前的内存
		arr = NULL;//指针置空
		return EXIT_FAILURE;
	}
	else//调整成功
	{
		arr = temp;//更新指针
		temp = NULL;//临时指针置空
	}

	for (; i < 20; i++)
	{
		arr[i] = i + 1;
	}

	for (i = 0; i < 20; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	free(arr);//释放开辟的内存块
	arr = NULL;//将指针置空
	return 0;
}

运行结果:

三、动态内存分配可能导致的一些问题

1、内存泄漏

这是动态内存分配中最常见的问题之一。内存泄漏发生在程序分配了内存但未能释放它,导致程序持续占用不再需要的内存。随着时间的推移,这可能导致程序和系统可用内存减少,严重时可能使系统变慢或崩溃。

解决方法:

这需要我们在使用完毕一块内存块时,要使用 free 函数来释放这个内存块。

2、内存碎片化

当我们多次使用 malloc 这类内存分配函数时,随着程序反复分配和释放不同大小的内存块,可用内存可能会分散为许多小块,这称为内存碎片。过度的内存碎片可能导致无法为大的内存请求分配连续的空间,即使有足够的总空闲内存,因为这时的总空闲内存是分散的,并不是连续的。

解决方法:

尽量减少频繁分配和释放小块内存的操作。
使用内存池。

3、重复释放一个内存块

如果尝试释放同一块内存两次或多次,可能会导致程序运行错误或崩溃。双重释放可能破坏内存分配器的数据结构,导致未定义行为。

int* arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL)
{
	printf("%s\n", strerror(errno));
	return EXIT_FAILURE;
}
free(arr);
free(arr);

解决方法:

再第一次释放内存后将指针置空,因为如果给free传空指针,free会什么也不做。

4、野指针

当内存被释放后,指向该内存的指针称为野指针,因为它指向的内存区域不再有效。继续使用这样的指针可能会导致不可预测的行为或程序崩溃。

解决方法:

在释放内存块后,将之前指向这块内存块的指针置空。

5、对NULL指针进行解引用

对于 malloc 这类内存分配函数,再内存分配不成功时,会返回NULL指针,这时如果没有进行对返回结果的检查,则可能发生对NULL指针的解引用操作,这时错误的。

解决方法:

对函数返回结果进行检查。

6、对非动态开辟空间使用free函数

对非动态开辟内存空间使用free函数释放空间可能导致者程序崩溃等等。

解决方法:

认真审视代码。

7、使用free释放部分动态开辟的内存块

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

int main()
{
	int* arr = (int*)malloc(10 * sizeof(int));
	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	arr++;//指针向后移了一个步长
	free(arr);
	arr = NULL;
	return 0;
}

上面的示范代码就尝试只是放一部分动态开辟的空间,这时错误的,可能会导致程序崩溃。

解决方法:

尽量不要移动指向内存块的指针,如果要移动尽量定义一个备份指针保存这个内存块的地址。确保总是释放完整的内存块,而不是部分内存。

四、动态内存分配的通讯录

在了解了这些动态内存管理的函数后,我们可以对之前的静态通讯录稍微改动一下,实现对内存的更高效的使用。

该项目包含三个文件:

contacts_list.c/cpp

#include "contacts_list.h"

void menu()
{
	printf("*************************************\n");
	printf("*****  1.add            2.del    ****\n");
	printf("*****  3.search         4.modify ****\n");
	printf("*****  5.display        6.sort   ****\n");
	printf("*****  0.exit                    ****\n");
	printf("*************************************\n");
	printf("please choose a option>:");
}

int InitContact(Contacts_list* list)
{
	list->capacity = DEFUALT_CONTACT;//容量设为DEFUALT_CONTACT
	list->num = 0;//联系人个数设为0
	list->dataPtr = (PeopleInfo*)malloc(DEFUALT_CONTACT * sizeof(PeopleInfo));//开辟DEFUALT_CONTACT个联系人大小的内存块
	if (list->dataPtr == NULL)//分配失败
	{
		printf("InitContact:%s\n", strerror(errno));//抛出错误信息
		return 1;
	}
	//分配成功
	return 0;
}

int IncreaseCapacity(Contacts_list* list, int num)
{
	PeopleInfo* temp = (PeopleInfo*)realloc(list->dataPtr, (list->capacity + num) * sizeof(PeopleInfo));
	if (temp == NULL)//分配失败
	{
		printf("IncreaseCapacity:%s\n", strerror(errno));//抛出错误信息
		return 1;
	}
	else//分配成功
	{
		list->dataPtr = temp;//更新内存块的指针
		temp = NULL;//将临时指针置空
		list->capacity += num;//更新容量
	}
	return 0;
}

void AddContact(Contacts_list* list)
{
	//增容
	if (list->num == list->capacity)
	{
		IncreaseCapacity(list, DEFUALT_ALLOC_CONTACT);//默认增容DEFUALT_ALLOC_CONTACT大小
	}

	//录入信息
	printf("please type in name(maximum 20)>:");
	scanf("%s", list->dataPtr[list->num].name);
	printf("please type in gender(maximum 8)>:");
	scanf("%s", list->dataPtr[list->num].gender);
	printf("please type in age>:");
	scanf("%d", &(list->dataPtr[list->num].age));
	list->num++;
	printf("AddContact successfully\n");
}

void DisplayContact(const Contacts_list* list)
{
	int i = 0;
	printf("------------------------------------------------\n");
	printf("%-20s%-8s%-s\n", "name", "gender", "age");
	for (i = 0; i < list->num; i++)
	{
		printf("%-20s%-8s%d\n", list->dataPtr[i].name, list->dataPtr[i].gender, list->dataPtr[i].age);
	}
	printf("------------------------------------------------\n");
	printf("display successfully\n");
}

void DestroyContact(Contacts_list* list)
{
	free(list->dataPtr);//释放开辟的空间
	list->dataPtr = NULL;//将指向内存块指针置空
}

void DeleteContact(Contacts_list* list)
{
	char name[21];
	if (list->num == 0)
	{
		printf("there is no contact can be deleted\n");//联系人为零,无需删除
		return;
	}

	printf("please type in the name who you want to delete>:");
	scanf("%s", name);
	int res = SearchContact(list, name);

	if (res != -1)
	{
		int i = 0;
		for (i = res; i < list->num - 1; i++)
		{
			list->dataPtr[i] = list->dataPtr[i + 1];
		}
		list->num--;
		printf("delete successfully\n");
	}
	else
	{
		printf("cannot find\n");//没找到要删除的联系人
		return;
	}
}

int SearchContact(Contacts_list* list, const char* string)
{
	int i = 0;
	for (i = 0; i < list->num; i++)
	{
		if (strcmp(list->dataPtr[i].name, string) == 0)
		{
			return i;//查找到返回下标
		}
	}
	return -1;//未查找到返回-1
}

void ModifyContact(Contacts_list* list, const char* string)
{
	int res = SearchContact(list, string);
	if (res == -1)
	{
		printf("cannot find\n");
		return;
	}
	printf("please type in name(maximum 20)>:");
	scanf("%s", list->dataPtr[res].name);
	printf("please type in gender(maximum 8)>:");
	scanf("%s", list->dataPtr[res].gender);
	printf("please type in age>:");
	scanf("%d", &(list->dataPtr[res].age));

	printf("modification successfully\n");
}

int cmp_by_name(const void* element1, const void* element2)
{
	PeopleInfo* ele1 = (PeopleInfo*)element1;//强制转换为联系人结构体类型,方便访问name变量
	PeopleInfo* ele2 = (PeopleInfo*)element2;
	return strcmp(ele1->name, ele2->name);
}

int cmp_by_age(const void* element1, const void* element2)
{
	int res = ((PeopleInfo*)element1)->age - ((PeopleInfo*)element2)->age;
	return ((-res < 0) - (res < 0));
}

void SortContact(Contacts_list* list, int num)
{
	switch (num)
	{
	case 1:
		qsort(list->dataPtr, list->num, sizeof(PeopleInfo), cmp_by_name);
		printf("sort successfully\n");
		break;
	case 2:
		qsort(list->dataPtr, list->num, sizeof(PeopleInfo), cmp_by_age);
		printf("sort successfully\n");
		break;
	default:
		printf("error\n");
		break;
	}
}

contacts_list.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//默认联系人个数
#define DEFUALT_CONTACT 3
//联系人空间不够时,默认分配空间个数
#define DEFUALT_ALLOC_CONTACT 3

//打印菜单函数
void menu();

//声明联系人信息结构体变量
typedef struct PeopleInfo
{
	char name[21];
	char gender[9];
	int age;
}PeopleInfo;

//声明通讯录结构体变量
typedef struct Contacts_list
{
	PeopleInfo* dataPtr;//存放联系人数据,指向内存块的指针
	int num;//记录通讯录联系人个数
	int capacity;//记录通讯录容量
}Contacts_list;

//初始化函数,默认初始化DEFUALT_CONTACT个联系人的空间,分配成功返回0,失败返回1
int InitContact(Contacts_list* list);

//增容函数,增加num大小,成功返回0,失败返回1
int IncreaseCapacity(Contacts_list* list,int num);

//添加联系人,如果空间不足,默认再开辟DEFUALT_ALLOC_CONTACT个联系人的空间,分配成功返回0,失败返回1
void AddContact(Contacts_list* list);

//摧毁通讯录,释放开辟的空间
void DestroyContact(Contacts_list* list);

//显示通讯录
void DisplayContact(const Contacts_list* list);

//删除联系人
void DeleteContact(Contacts_list* list);

//查找联系人
int SearchContact(Contacts_list* list, const char* string);

//修改联系人
void ModifyContact(Contacts_list* list, const char* string);

//排序联系人
void SortContact(Contacts_list* list, int num);

//按名字排序
int cmp_by_name(const void* element1, const void* element2);

//按年龄排序
int cmp_by_age(const void* element1, const void* element2);

test.c/cpp

#include "contacts_list.h"

int main()
{
	int status = 0;

	Contacts_list contacts_list;//通讯录结构体变量

	if (InitContact(&contacts_list))//初始化失败会返回1,如果初始化失败则程序以1返回
	{
		printf("Initialization Error\n");
		return 1;
	}

	int res = 0;
	int num = 0;
	do
	{
		menu();//打印菜单
		scanf("%d", &status);
		system("cls");
		switch (status)
		{
		case 1:
			AddContact(&contacts_list);
			break;
		case 2:
			DeleteContact(&contacts_list);
			break;
		case 3:
			char name1[21];
			printf("please type in the name>:");
			scanf("%s", name1);
			res = SearchContact(&contacts_list, name1);
			if (res != -1)
			{
				printf("%d\n", res);
			}
			else
			{
				printf("cannot find\n");
			}
			break;
		case 4:
			char name2[21];
			printf("please type in the name>:");
			scanf("%s", name2);
			ModifyContact(&contacts_list, name2);
			break;
		case 5:
			DisplayContact(&contacts_list);
			break;
		case 6:
			printf("1.sort by name\n");
			printf("2.sort by age\n");
			scanf("%d", &num);
			SortContact(&contacts_list, num);
			break;
		case 0:
			DestroyContact(&contacts_list);//释放空间,摧毁通讯录
			printf("exit\n");
			break;
		default:
			printf("error\n");
			break;
		}
	} while (status);

	return 0;
}
  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值