C语言进阶——动态内存管理

作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:C语言从入门到进阶

语录:Stay hungry stay foolish

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册和我一起刷题吧 

文章目录

1. 为什么存在动态内存

2. 分配动态内存函数的介绍

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

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

4. 几个经典的笔试题

笔试题1 

笔试题2

笔试题3

笔试题4

5. C/C++程序的内存开辟

6. 动态通讯录


 

1. 为什么存在动态内存

我们经常用到的开辟内存方式有:

int a = 40;
int arr[40] = {0}; 

 上述开辟内存方式的特点:

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

2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

有时候我们需要的空间大小是在程序运行时才能知道,上述方式满足不了要求,所以出现了动态内存的开辟

2. 分配动态内存函数的介绍

2.1 malloc

C语言提供了一个动态内存开辟的函数: 

void* malloc (size_t size);

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

如果开辟成功,则返回一个指向开辟好空间的指针

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

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

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

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i=0;i<10;i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
	printf("%d ", *(p + i));
	}
	printf("\n");

	return 0;
}

848ca1998df74390bfaff8ede003f359.png

 这里没有free,当程序退出的时候, 系统会回收该空间

2.2 free

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

void free (void* ptr);

free函数用来释放动态开辟的内存

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

如果参数ptr是NULL指针,则函数什么事都不做

malloc和free都声明在stdlib.h头文件中

int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i=0;i<10;i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
	printf("%d", *(p + i));
	}
    
    //释放
    free(p);
    p = NULL;

	return 0;
}

不用free函数释放空间会出现内存泄漏,free回收完系统空间时,p还是指向那块吧被释放的空间,为了避免出现野指针的问题,一定要将它置为空指针

2.3 calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

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

与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)calloc(10,sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	/*int i = 0;
	for (i=0;i<10;i++)
	{
		*(p + i) = i;
	}*/
	int i = 0;
	for (i = 0; i < 10; i++)
	{
	printf("%d ", *(p + i));
	}
	printf("\n");
	free(p);
	p = NULL;

	return 0;
}

67e9dd9e0aa34510a0db56910c5dd5af.png

 所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务

2.4 realloc

realloc函数的出现让动态内存管理更加灵活

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下:

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

ptr是要调整的内存地址

size调整之后新大小

返回值为调整之后的内存起始位置

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc在调整内存空间的是存在两种情况:

情况1:

当是情况1 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址,旧的地址和数据都会被自动释放

情况2:

当是情况2 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

e3c1f12f1fbc4a98ae639a492846bfac.png

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i=0;i<10;i++)
	{
		*(p + i) = i+1;
	}
	//扩容
	//追加40个字节
	int *ptr = (int* )realloc(p, 80);

	if (ptr != NULL)
	{
		p = ptr;
	}
	//使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
	free(p);
    p = NULL;

	return 0;
}

注意:开辟多了会出现内存碎片,导致内存利用率下降,程序的效率也会下降 

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	*p = 20;
	free(p);
	p = NULL;
	return 0;
}

要判断是否为空指针,如果是空指针就是开辟内存失败,出现对空指针的解引用

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
	}
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		p[i] = i;
	}
	
	free(p);
	p = NULL;
	return 0;
}

206c2c207c70494ea67e266b971c4e02.png

 当i是10的时候越界访问

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

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

free函数只能对在堆上开辟的动态内存进行释放

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
	}
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

1199ca345ec348909e73158280dd3b06.png

 程序运行起来,p已经不指向最开始的地址,因此最后释放,也不会将动态开辟的内存全部释放

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

int main()
{

	int* p = (int*)malloc(40);

	free(p);
	//.....
	free(p);

	return 0;
}

重复释放并且没有将p置为空指针,会报错

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

void test()
{
	int* p = (int*)malloc(40);
	//....
	int a = 0;
	scanf("%d", &a);
	//...
	if (a == 10)
		return;

	free(p);
	p == NULL;
}

int main()
{

	test();

	return 0;
}

当满足a==10,free是没有机会被执行的,并且函数结束就找不到该空间的地址了,也不会释放,内存出现泄漏

注意:忘记释放不再使用的动态开辟的空间会造成内存泄漏

动态开辟的空间一定要释放,并且正确释放

4. 几个经典的笔试题

运行Test 函数会有什么样的结果

笔试题1 

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

解析:

f2d53cd64e6b468b9e150301de7c6242.png

笔试题2

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

{
    char*str = NULL;
    str = GetMemory();
    printf(str);

}

 解析:

41b7f6512c0e461a8105aa277ffe72b1.png

笔试题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);
}

 分析:

1f403674335843aaa6e815e72f284ffa.png

笔试题4

void Test(void)
{ 
    char* str = (char*) malloc(100);
    strcpy(str, "hello");
    free(str);

if(str != NULL)
 { 
    strcpy(str,"world");
    printf(str);
 }
}

分析:

9e767e7d42064cda8552051c66a8c7f1.png

5. C/C++程序的内存开辟

e7d0120d11704429be7d9e4ed041442a.png

6. 动态通讯录

动态通讯录:默认存放三个人的信息,不够则扩容,每次增加两个人的空间,在静态通讯录的基础上修改即可

参看静态通讯录

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX  10
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_Sz 3
#define INC_SZ 2
//类型的声明
// 
//只是一个人的信息
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[100];//存放人的信息
//	int count;//记录当前通讯录有多少人的信息
//}Contact;
// 
// 
//动态版本
typedef struct Contact
{
	PeoInfo* data;//存放人的信息
	int count;//记录当前通讯录有多少人的信息
	int capacity;//记录当前通讯录容量

}Contact;

//初始化通讯录函数
int InitContact(Contact *pc);

//增加联系人到通讯录
void addContact(Contact* pc);

//打印通讯录信息
void showContact(const Contact* pc);

//删除指定联系人
void delContact(Contact* pc);

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

//修改指定联系人
 void modifyContact(Contact* pc);

 //按照名字排序通讯录内容
 void sortContact(Contact* pc);
 //销毁通讯录
 void DestroyContact(Contact* pc);

//动态版本
int InitContact(Contact* pc)
{
	pc->count = 0;
	pc->data = (PeoInfo*)calloc(DEFAULT_Sz,sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		printf("InitContact:%s\n", strerror(errno));
		return 1;
	}
	pc->capacity = DEFAULT_Sz;
	return 0;
}

 实现增容功能:

//增容函数
void CheckCapacity(Contact* pc)
{
	if (pc->count == pc->capacity)
	{
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("addContact:%s\n", strerror(errno));
		}
		else
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("增容成功\n");
		}
	}
}


//动态版本
void addContact(Contact* pc)
{
	assert(pc);
	//增容
	CheckCapacity(pc);
	
	//添加信息
	printf("\n请输入名字:>");
	//每次放进去的信息都是放进data 下标为count的数组
	scanf("%s", pc->data[pc->count].name);

	printf("\n请输入年龄:>");
	//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
	//这里的年龄是int 型变量,需要取地址
	scanf("%d", &(pc->data[pc->count].age));

	printf("\n请输入性别:>");
	scanf("%s", pc->data[pc->count].sex);

	printf("\n请输入电话:>");
	scanf("%s", pc->data[pc->count].tele);

	printf("\n请输入地址:>");
	scanf("%s", pc->data[pc->count].addr);

	pc->count++;
	printf("\n增加成功\n");

}

 free增容所开辟的空间:


//销毁通讯录
void DestroyContact(Contact* pc)
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
}

 “ 本期的分享就到这里了, 记得给博主一个三连哈,你的支持是我创作的最大动力!”

 

  • 60
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 116
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YoLo♪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值