动态内存管理-c语言

news2024/10/23 21:36:56

目录

1.为什么要有动态内存分配

2.malloc函数和free函数

malloc

函数原型

栗子

free

函数原型

栗子

3.calloc和***realloc***

3.1calloc函数

原型如下:

栗子

3.2***recalloc***

第一种情况

第二种情况

第三种情况

recalloc模拟实现calloc函数

4.六大常⻅的动态内存的错误

4.1对NULL指针的解引⽤操作

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

 4.3对⾮动态开辟内存使⽤free释放

 4.4使⽤free释放⼀块动态开辟内存的⼀部分

 4.5对同⼀块动态内存多次释放

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

 5.动态内存经典笔试题分析

5.1题目一

正确解法

5.2题目二

正确解法

5.3题目三

正确解法

5.4题目四

正确解法

6. 柔性数组

6.1什么是柔性数组

栗子

6.2柔性数组的使用

6.3柔性数组的优势


1.为什么要有动态内存分配

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

但是上述的开辟空间的⽅式有两个特点:

• • 空间开辟⼤⼩是固定的。

* *数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知 道,那数组的编译时开辟空间的⽅式就不能满⾜了。 C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

2.malloc函数和free函数

malloc

是c语言用于动态内存开辟的函数

函数原型

void* malloc (size_t size);

说明:malloc向系统申请分配指定size个字节的内存空间。返回类型是void *类型。void *类型表示未确定类型的指针。C、C++规定,void *类型可以强制转换为任何其他类型的指针

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

******#include<stdlib.h>//malloc头文件

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

*******如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查。

*******void* 所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候⾃⼰来绝定。

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

栗子

因为malloc()返回值是返回函数开辟空间的地址,类型是void,所以p应该是要用void * p来接收,但是void类型的指针,是不能进行解引用的,所以这个方法行不通。

解决办法

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

要把malloc返回值进行强制转换成你要开辟空间用于什么数据类型的类型。

比如这里我需要int类型的内存,所以这里要转换成int类型。

重要一步检查是否内存开辟成功也莫忘:

如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针

int* p = (int *)malloc(40);
if (p == NULL)
{
	perror("malloc");//报错
	return 1;
}
//使用开好的内存
for (int i = 0; i < 10; i++)
{
	/**p = i;
	p++;*/
	//上面写法,写到后面。p位置不好找回来
	*(p + i) = i;//这样写p就不会动了
	
}

for (int i = 0; i < 10; i++)
{
	printf("%d ", *(p+i));
}

free


函数原型

void free (void* ptr);

free参数是动态内存申请好返回的指针变量。

函数free,专⻔是⽤来做动态内存的释放和回收的

**free函数必须和malloc函数同时使用不然会报错

**free函数无法释放栈内存的变量

**free只能释放动态内存申请的空间

栗子

int* p = (int *)malloc(40);
if (p == NULL)
{
	perror("malloc");//报错
	return 1;
}
//使用开好的内存
for (int i = 0; i < 10; i++)
{
	/**p = i;
	p++;*/
	//上面写法,写到后面。p位置不好找回来
	*(p + i) = i;//这样写p就不会动了
	
}

for (int i = 0; i < 10; i++)
{
	printf("%d ", *(p+i));
}
free(p);
p = NULL;//因为释放了内存,但是 p指向的位置还是内存的起始地址,避免成为野指针,要指向空。

3.calloc和***realloc***

3.1calloc函数

原型如下:

void* calloc (size_t num, size_t size);

****函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

***malloc与calloc区别就是malloc不会初始化,而calloc初始化为0
***第二就是,参数不一样,calloc可以输入指定个数,和大小

栗子

//使用calloc函数进行动态内存申请
int* p = (int*)calloc(10, sizeof(int));

3.2***recalloc***

realloc函数可以做到对动态开辟内存⼤小调整;

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

函数原型

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

realloc分为三种情况,

第一种情况

申请空间失败;

第二种情况

申请成功,但开辟空间的时候后面没有足够的空间,在这种情况下,realloc函数会在内存的堆区重新找一个空间(这个空间满足新的申请空间大小需求),同时会把旧的数据拷贝到新的空间,然后释放旧的空间,同时返回新的空间的起始地址。

第三种情况

也是成功情况,但是这次后面的空间是足够的情况,可以直接进行扩大,扩大空间后,直接返回旧的空间的起始地址。

//要用一个临时变量存储申请的空间,防止申请失败而置为null
int* p = (int*)calloc(10, sizeof(int));
int ptr = (int*)reallco(p, 10 * sizeof(int));
if (ptr != NULL)
{
	p = ptr;
}
else
{
	perror("realloc");
	return 1;
}

recalloc模拟实现calloc函数

int* p = (int *)realloc(NULL,40);
if (p == NULL)
{
	perror("malloc");//报错
	return 1;
}
//使用开好的内存
for (int i = 0; i < 10; i++)
{
	/**p = i;
	p++;*/
	//上面写法,写到后面。p位置不好找回来
	*(p + i) = i;//这样写p就不会动了
	
}

for (int i = 0; i < 10; i++)
{
	printf("%d ", *(p+i));
}
free(p);
p = NULL;//因为释放了内存,但是 p指向的位置还是内存的起始地址,避免成为野指针,要指向空。

4.六大常⻅的动态内存的错误

4.1对NULL指针的解引⽤操作

没有进行判断是否申请内存成功而直接使用

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

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

int* p = (int*)malloc(40);
//类似于数组的越界访问
if (NULL == p)
{
	exit(EXIT_FAILURE);
}
for (int i = 0; i <= 10; i++)
{
	//i<=10,就是越界访问了,因为申请的大小才10个int大小
	//i<10才行。
	*(p + i) = i;
}
free(p);
p = NULL;

 4.3对⾮动态开辟内存使⽤free释放

int a = 0;
int* p = (int*)malloc(40);
if (NULL == p)
{
	exit(EXIT_FAILURE);
}
//这里就出错了
p = &a;
//p指向a地址,此时就丢失了开辟的空间,形成空间泄露的情况。
free(p);
p = NULL;

 4.4使⽤free释放⼀块动态开辟内存的⼀部分

//只释放一部分的动态内存,也会错误,free只能放内存的首地址。
int* p = (int*)malloc(40);
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	p ++;
	free(p);
	p = NULL;

 4.5对同⼀块动态内存多次释放

 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
 

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

:malloc/calloc/realloc 申请的空间如果不主动释放,出了作用域是不会销毁的
释放的方式:
1.free主动释放

2.直到程序结束,才由操作系统回收

 void test()
 {
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
 }
 int main()
 {
 test();
 while(1);
 }

 5.动态内存经典笔试题分析

5.1题目一

void GetMemory(char* p)
{
	p = (char*)malloc(100);//而这里申请的空间是p的,
	//后面这个函数运行结束,p就会消失,申请的内存也就会泄露,导致程序崩溃
}
int main()
{
	char* str = NULL;
	GetMemory(str);//传过去的是str,并不是地址
	strcpy(str, "hello world");//str并没有申请有内存,却直接进行解引用,会导致程序崩溃。
	printf(str);
	free(str);
	str = NULL;
}

str并没有申请有内存,却直接进行解引用,会导致程序崩溃。

正确解法

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
int main()
{
	char* str = NULL;
	GetMemory(&str);//这里传地址过去就好了
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

5.2题目二

char* GetMemory(void)
{
	char p[] = "hello world";//这里创建的内存,如果函数运行完成就会自动销毁了
	return p;//那么此时p就是野指针。
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

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

static用法讲解

正确解法

char* GetMemory(void)
{
    //static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长。
	static char p[] = "hello world";//这里创建的内存,如果函数运行完成就会自动销毁了
	return p;//那么此时p就是野指针。
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

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

5.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);
 }

这里就是缺少了free销毁申请的内存,会造成内存泄露

正确解法

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
	if (*p == NULL)
	{
		return 0;
	}
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	printf("韦坤四战\n");
	Test();
	return 0;
}

5.4题目四

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

这里错的是free释放掉str后,并没有手动把str指向null;

正确解法

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

6. 柔性数组

6.1什么是柔性数组

满足下面三个条件即是柔性数组:

1.在结构体中

2.结构体中最少两个成员,并且它是最后一个成员

3.是一个未知大小的数组

柔性数组的特点:

 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。

包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤ ⼩,以适应柔性数组的预期⼤⼩。

栗子

struct st_type
{
	int i;
	/*1.在结构体中
	2.结构体中最少两个成员,并且它是最后一个成员
	3.是一个未知大小的数组*/
	int a[0];//柔性数组成员
}type_a;

int main()
{
	printf("%d\n", sizeof(type_a));//只会计算i的大小为4,数组a[0]是未知的无法计算。
	return 0;
}

6.2柔性数组的使用

//申请动态内存
struct st *ps  = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int));//10 * sizeof(int)这里就是给柔性数组开的内存。
if (ps = NULL)
{
	perror("ps");
	return 0;
}
//赋值
ps->i = 10;
for (int i = 0; i < 10; i++)
{
	ps->a[i] = i;
}

6.3柔性数组的优势

//柔性数组的优势
//就是可以随时扩大柔性数组的大小。
//申请动态内存
struct st *ps  = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int));//10 * sizeof(int)这里就是给柔性数组开的内存。
if (ps = NULL)
{
	perror("ps");
	return 0;
}
//赋值
ps->i = 10;
for (int i = 0; i < 10; i++)
{
	ps->a[i] = i;
}

struct st *ptr = (struct st*)realloc(ps, sizeof(struct st) + 15 * sizeof(int));
if (ps = NULL)
{
	perror("ps");
	return 0;
}
else
{
	ps = ptr;
}

第⼀个好处是:⽅便内存释放

 如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤ ⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能 指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返 回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。

第⼆个好处是:这样有利于访问速度. 连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你 跑不了要⽤做偏移量的加法来寻址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1498153.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springboot+vue的球队训练信息管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

“揭秘网络握手与挥别:TCP三次握手和四次挥手全解析“

前言 在计算机网络中&#xff0c;TCP&#xff08;传输控制协议&#xff09;是一种重要的通信协议&#xff0c;用于在网络中的两台计算机之间建立可靠的连接并交换数据。TCP协议通过“三次握手”和“四次挥手”的过程来建立和终止连接&#xff0c;确保数据的准确传输。 一、三…

2024年腾讯云优惠政策_腾讯云服务器特价购买活动入口

腾讯云优惠活动2024新春采购节活动上线&#xff0c;云服务器价格已经出来了&#xff0c;云服务器61元一年起&#xff0c;配置和价格基本上和上个月没什么变化&#xff0c;但是新增了8888元代金券和会员续费优惠&#xff0c;腾讯云百科txybk.com整理腾讯云最新优惠活动云服务器配…

数据结构(八)——初识单链表

&#x1f600;前言 单链表是数据结构中最基本的一种链表结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含数据和指向下一个节点的指针。单链表具有灵活性和动态性&#xff0c;可以根据需要插入、删除和查找元素&#xff0c;适用于各种场景和问题的解决。 在本篇文章…

网络编程 · 代码笔记1

目录 前言1、编程环境2、编译命令3、标题名前缀解释4、注意事项 0011客户端读取服务端字符_服务端0012客户端读取服务端字符_客户端0021回声测试_服务端0022回声测试_客户端0030启动端口复用解决端口绑定失败问题0041服务器不间断进行侦听通信_服务端0042服务器不间断进行侦听通…

liunx操作系统 环境变量

环境变量 main函数参数 命令行参数环境变量 环境变量的查看环境变量的获取 main函数参数 命令行参数 main函数是有参数的&#xff0c;只是我们一般不适用 这是main函数从bash中读取进程数据使用的一个基本入口。 下面进行简单演示。 o 好oo都是我们输入的命令行参数。其实&a…

如何查看前端的vue项目是vue2还是vue3项目

1. 检查package.json文件 在项目的根目录下&#xff0c;打开package.json文件&#xff0c;查找dependencies或devDependencies部分中的vue条目。版本号将告诉你是Vue 2还是Vue 3。例如&#xff1a; Vue 2.x: "vue": "^2.x.x"Vue 3.x: "vue": &…

【Linux基础(二)】进程管理

学习分享 1、程序和进程1.1、程序1.2、进程和进程ID 2、Linux下的进程结构3、init进程4、获取进程标识5、fork系统调用5.1、fork函数实例分析 6、进程的特性7、在Linux下进程指令7.1、终止进程指令7.2、查看进程指令&#xff1a;7.3、以树状图列出进程 8、多进程运行异常情况8.…

判断连续数据同意特征的方法:插旗法

bool isMonotonic(int* nums, int numsSize) {int flag 2;for (int i 1; i < numsSize; i) {if (nums[i-1] > nums[i]) {if (flag 0)return false;flag 1;}else if (nums[i-1] < nums[i]) {if (flag 1)return false;flag 0;}}return true; }此代码较为简单&…

Vue中如何处理组件间的耦合问题?

在Vue中处理组件间的耦合问题是前端开发中常见的挑战之一。耦合问题指的是组件之间的依赖关系过于紧密&#xff0c;一旦某个组件发生改动&#xff0c;则可能导致其它组件也需要作出相应调整。为了解决这个问题&#xff0c;我们可以采取以下几种方法&#xff1a; 使用事件总线&…

牛客每日一题之 前缀和

目录 题目介绍&#xff1a; 算法原理&#xff1a; 前缀和&#xff1a; 代码实现&#xff1a; 题目介绍&#xff1a; 题目链接&#xff1a;【模板】前缀和_牛客题霸_牛客网 算法原理&#xff1a; 先讲讲暴力解法每次求出数组下标r之前元素的和&#xff0c;再减去数组下标l-…

Docker容器的操作

目录 运行容器 查看容器 查看容器详细信息 删除容器 启动容器 停止容器 重启容器 暂停容器 激活容器 杀死容器 进入容器 常用 查看容器的日志 拷贝容器的文件到本地 容器改名 查看容器资源 查看容器内部的进程 监测容器发生的事件 检测容器停止以后的反回值…

vue3 + vite全局引入element-plus后使用 Message 消息提示

安装Element plus&#xff1a; pnpm install element-plus 安装unplugin-element-plus 自动导入组件 pnpm i -D unplugin-auto-import pnpm install -D unplugin-element-plus vite.config.ts 使用plugins数组里配置&#xff0c;自动导入 import { fileURLToPath, URL } fro…

【应用多元统计分析】--多元数据的描述和展示(R语言)

一元随机变量 我们用协方差来刻画两个变量的相关关系&#xff0c;这里指的是线性相关关系。 对于一元随机变量的可视化最简单的就是散点图&#xff0c;大致可以看出X和Y之间的相关关系。如果想更好的看X、Y之间的相关关系&#xff0c;可以画二维的散点图。 总结&#xff1a; 均…

蓝桥杯物联网竞赛_STM32L071_11_知识体系的查漏与补缺

太久没学单片机了&#xff0c;再重新过一遍查漏补缺&#xff0c;对其中之前没怎么在意的&#xff0c;而现在又发觉的问题进行再分析与补充 1. debug serial wire是干什么用的 这个东西我勾选不勾选都对我的程序没有什么影响&#xff0c;我很好奇是干什么用的&#xff0c;网上查…

【PCIe】初识PCIe

&#x1f525;博客主页&#xff1a;[PannLZ] &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 文章目录 PCIe简介PCIe速度 PCIe简介 计算机内部有很多电子元器件&#xff0c;他们之间会有数据沟通和传输的需求。如果A元件想给B元件传输数据&am…

JavaWeb04-Request,Response

目录 一、Request&#xff08;请求&#xff09; 1.作用 2.继承体系 3.获取请求数据 &#xff08;1&#xff09;请求行 &#xff08;2&#xff09;请求头 &#xff08;3&#xff09;请求体&#xff08;POST&#xff09; &#xff08;5&#xff09;Request通用方式获取请求…

Unity用Shader将一张图片变成描边图片素描风格。

环境&#xff1a; unity2021.3.x 效果&#xff1a; 实现核心思路(shader)&#xff1a; fixed4 frag (v2f i) : SV_Target {fixed4 col tex2D(_MainTex, i.uv);// 调整相似度bool isRedMatch abs(col.r - _TargetColor.r) < 0.15;bool isGreenMatch abs(col.g - _Target…

消息队列-Kafka-如何进行顺序消费

全局有序 只有 1 个分区&#xff0c;那这个时候就是能够保证消息的顺序消费。 分区有序 如果我们还是想同时消费多个分区并且保证有序&#xff0c;这个时候我们需要将需要保证顺序的消息路由到同一个分区。 在发送消息的时候我们可以看到&#xff1a; 上面的代码定义了消息…

【R语言实战】聚类分析及可视化

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…