当前位置: 首页 > article >正文

C语言——动态内存管理

目录

一为什么要动态内存管理

二内存函数

1malloc

2free

3calloc

4realloc

三创建错误

1没有判断直接使用

2对开辟的空间越界访问

3对非开辟的内存进行释放

4只释放开辟内存的一部分

5对同一块开辟内存多次释放

6忘记释放开辟的内存(内存泄漏)

四常见笔试题

题1

题2

题3

五C/C++程序的内存开辟 

六对通讯录进行改造

七柔性数组

1特点

2使用


一为什么要动态内存管理

前面已经掌握了开辟内存的方式:
 

int val = 20;//在栈空间上开辟四个字节

但这种开辟内存的方式有两个特点:

1. 空间开辟大小是固定的;
2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

如果一个数组在使用时发现不够用(前面通讯录信息存满了还要存信息),我们就要进行来学习动态内存开辟内存来满足需求~

二内存函数

1malloc

a 这个函数向内存(堆区)申请一块连续可用的空间,并返回指向这块空间的指针;
b 如果开辟成功,则返回一个指向开辟好空间的指针
c 如果开辟失败,则返回一个NULL指针;因此malloc的返回值一定要做检查
d 返回值的类型是 void* ,因为malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定设置
e 如果参数 size 为0,malloc的行为是标准是未定义的,现象取决于编译器。

2free

自己申请的内存一定要记得释放,所以有了free函数:

free函数专门用来释放动态开辟的内存;
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的(报错);
如果参数 ptr 是NULL指针,则函数什么事都不做;

所以使用free后要把指针置NULL(不然就是野指针)

使用:

#include<stdio.h>

int main()
{
	int *ptr = (int*)malloc(10 * sizeof(int));
	if (NULL == NULL)//判断ptr指针是否为空
	{
		perror("malloc error");
		return 1;
	}
	//使用...

	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//一定要记得置空
	return 0;
}

3calloc

calloc函数与malloc不同的一点是:开的空间有帮你进行初始化为0;                                                参数方面:比malloc更细(不用你去算总共开辟空间的字节数):开num个大小为size的空间

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

int main()
{
	int* ptr1 = (int*)calloc(5, sizeof(int));
	int* ptr2 = (int*)malloc(5*sizeof(int));
	if (ptr1 == NULL || ptr2 == NULL)//判断ptr指针是否为空
	{
		perror("malloc error");
		return 1;
	}
	printf("ptr1=");
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", ptr1[i]);
	}
	printf("\nptr2=");
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", ptr2[i]);
	}
	free(ptr1);
	ptr1 = NULL;
	free(ptr2);
	ptr2 = NULL;
	return 0;
}

4realloc

realloc的出现让内存管理更加灵活!

ptr传入要调整的内存首元素地址,size是调整后的新大小

返回值为调整后的新内存的起始地址

realloc在调整内存空间是会存在以下两种情况:

a.原来空间后面有足够大的空间可以用来开辟(通常情况)

b.原来空间后面没有足够大的空间,需要realloc自己重新开辟新空间;

此时realloc:

1.开辟新空间后,将旧的数据拷贝过来

2.释放旧空间

3.返回新空间的起始地址

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

int main()
{
	int* p = (int*)malloc(100*sizeof(int));
	if (p == NULL)
	{
		perror("malloc error");
		return 1;
	}
	//使用...
	//空间不够要进行扩容
	//int* p = (int*)realloc(p, 200 * sizeof(int));//这种不保险,有可能扩容失败!
	int* p1 = (int*)realloc(p, 20000*sizeof(int));
	if (p1 != NULL)
	{
		p = p1;
	}
	//使用...
	free(p);
	p = NULL;
	return 0;
}

三创建错误

1没有判断直接使用

2对开辟的空间越界访问

3对非开辟的内存进行释放

4只释放开辟内存的一部分

5对同一块开辟内存多次释放

6忘记释放开辟的内存(内存泄漏)

#include <stdio.h>

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);//程序不退出
}

四常见笔试题

指出以下代码存在的问题

题1

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

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

int main()
{
	Test();
	return 0;
}

1.调用GetMeonry()时传的是实参,函数结束时p空间销毁(申请的空间还在,但是此时不知道地址),此时str还是NULL,strcpy对NULL解引用造成非法访问

2.申请空间没有用free释放,会造成内存泄漏

题2

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

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

int main()
{
	Test();
	return 0;
}

 str接收p指针时,此时p指针指向的空间已经被销毁了,打印printf属于非法访问

题3

#include<stdio.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}

1进行malloc申请空间时没有对str进行判断;

2free释放str申请的空间后,还对str进行操作,属于非法访问(free后要将str置NULL

五C/C++程序的内存开辟 

C/C++程序内存分配的几个区域:

栈区(stack):

函数栈帧的创建与销毁都是在栈区进行的;

栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等;
堆区(heap):

一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收;
数据段(静态区)存放全局变量、静态数据。程序结束后由系统释放;

代码段:存放函数体(类成员函数和全局函数)的二进制代码

六对通讯录进行改造

在上篇文章的通讯录代码进行修改:

将通讯录起始空间为2,随着信息的增加,空间满后将进行扩容(每次扩容3)

//Contact.h

typedef struct Contact
{
	int sz;
	//Mess message[Max_Size];
	Mess* message;
	int capacity;
}Contact;

//Contact.c
/*if (con->sz == Max_Size)
{
	printf("Contact Is Full\n");
	return;
}*/
if (con->sz == con->capacity)
{
	//扩容
	Mess* ptr = realloc(con->message, (con->capacity + Default_Size)*sizeof(Mess));
	if (ptr != NULL)
	{
		con->message = ptr;
		con->capacity += Default_Size;
		printf("扩容成功\n");
	}
	else
	{
		perror("realloc error");
		return;
	}
}

void Destory(Contact* con)
{
	free(con->message);
	con->message = NULL;
	con->capacity = 0;
	con->sz = 0;
}

七柔性数组

也许你听说过柔性数组(flexible array)这个概念,但是却不对它有很多了解;

柔性数组:结构体中最后一个成员允许是未知大小的数组

typedef struct st_type
{
    int i;
    int a[0];//柔性数组成员
    int a[];//或者写成这种
}type_a;

1特点

a 结构中的柔性数组成员前面必须至少一个其他成员
sizeof 返回的这种结构大小不包括柔性数组的内存
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小(给到柔性数组储存)

 

2使用

实现:在结构体中,用数组来储存数据(数据大小未知)

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

//使用柔性数组
int main()
{
	typedef struct st_type
	{
		int t;
		int a[0];//柔性数组成员
		//int a[];另外写法
	}type_a;

	type_a* p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc error");
		return 1;
	}
	p->t = 100;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p->a[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p->a[i]);
	}
	//空间不够要进行扩容
	type_a* pa = (type_a*)realloc(p, sizeof(type_a) * 10 * sizeof(int));
	if (pa == NULL)
	{
		perror("realloc error");
		return 2;
	}
	p = pa;
	//使用...

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

//不使用柔性数组同样能实现以上功能
int main()
{
	typedef struct st_type
	{
		int t;
		int* a;
	}type_a;

	type_a* p = (type_a*)malloc(sizeof(type_a));
	if (p == NULL)
	{
		perror("malloc error");
		return 1;
	}
	p->t = 100;
	p->a = (int*)malloc(sizeof(int) * 5);//要malloc两次,麻烦
	if (p -> a == NULL)
	{
		perror("malloc error");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p->a[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p->a[i]);
	}
	//空间不够要进行扩容
	int* pb = (int*)realloc(p->a, 10 * sizeof(int));
	if (pb == NULL)
	{
		perror("realloc error");
		return 2;
	}
	p->a = pb;
	//使用...

    //要按顺序释放,麻烦
	free(p->a);
	p->a = NULL;
	free(p);
	p = NULL;
	return 0;
}

相较于使用柔性数组而言:

好处1:可对数组直接进行访问使用

而另一种做法要先malloc存放结构体,再malloc存放数组,需要两次才能访问数组,麻烦~;

好处2:内存释放只需1次,简单

而另一种做法要先把数组空间释放,再释放结构体空间(注意顺序还不能颠倒!),麻烦~

如果还想了解关于结构体的内容,欢迎点击: C语言结构体里的成员数组和指针

以上便是全部内容,有问题欢迎在评论区指出,感谢观看! 


http://www.kler.cn/a/504176.html

相关文章:

  • 机器学习头歌(第三部分-强化学习)
  • CAPL如何设置TCP/IP传输层动态端口范围
  • 【某大型互联网企业】软件测试面试经验分享(1 ~ 3年)
  • RTDETR融合[WACV 2024]的MetaSeg中的gmb模块
  • 前端多语言
  • 数据分析-使用Excel透视图/表分析禅道数据
  • pytorch小记(五):pytorch中的求导操作:backward()
  • 向u-boot提交补丁的流程
  • 【高可用自动化体系】自动化体系
  • [NOIP2007 提高组] 矩阵取数游戏
  • 如何物理连接Franka机械臂
  • 【Vim Masterclass 笔记14】S07L29 + L30:练习课08 —— Vim 文本对象同步练习(含点评课内容)
  • 分布式缓存redis
  • IDM-VTON效果测试
  • JavaScript中如何实现函数签名
  • 25/1/14 算法笔记<强化学习> CBR加强化学习
  • 容器技术全面攻略:Docker的硬核玩法
  • 从零到一:用 Flask 和 Docker 构建并部署一个简单的接口请求页面
  • SpringData-Redis缓存之RedisTemplate
  • 使用 OpenSSL 实现 SSL/TLS 握手的流程和 Demo 示例
  • 从玩具到工业控制--51单片机的跨界传奇【2】
  • 运维练习题2
  • STORM:从多时间点2D图像中快速重建动态3D场景的技术突破
  • WordPress如何配置AJAX以支持点击加载更多?
  • GPT(General Purpose Timer)定时器
  • 【STM32-学习笔记-2-】外部中断