动态内存管”家“

news2025/1/15 17:45:13

在这里插入图片描述

🐋动态内存管理

  • 🦖动态内存分配存在的意义
  • 🦖动态内存函数的介绍
    • 🐤malloc和free
    • 🐤calloc
    • 🐤realloc
  • 🦖常见动态内存错误
    • 🐤对空指针的解引用操作
    • 🐤对动态开辟空间的越界访问
    • 🐤对非动态开辟内存使用free
    • 🐤使用free释放动态开辟内存的一部分
    • 🐤对同一块动态内存多次释放
    • 🐤动态开辟的内存忘记释放(内存泄漏)
  • 🦖C/C++程序的内存开辟
  • 🦖柔性数组
    • 🐤柔性数组的特点
    • 🐤柔性数组的使用
    • 🐤柔性数组的优势
  • 🦖结语

🦖动态内存分配存在的意义

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

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

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

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

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态存开辟了。

🦖动态内存函数的介绍

🐤malloc和free

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

void* malloc(size_t size);

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

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

void free(void* ptr);

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

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在stdlib.h头文件中。
举个栗子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//代码1
	int num = 0;
	scanf("%d", &num);
	int arr[num] = { 0 };  //c89标准不支持变长数组 error, c99才支持的变长数组
	//代码2
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//有必要将ptr置为空指针,free不会将ptr置为空指针,
			   //避免ptr为野指针,我们使用。
	return 0;
}

🐤calloc

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

void* calloc (size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		//使用空间
	}
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

🐤realloc

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的分配内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型如下:

void* realloc (void* ptr, size_t size);
  • ptr 是要调整的内存地址。
  • size 调整之后新大小。
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。
  • realloc在调整内存空间的是存在两种情况:
    • 情况1:原有空间之后有足够大的空间。
      在这里插入图片描述
  • 当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
    • 情况2:原有空间之后没有足够大的空间。
      在这里插入图片描述
  • 当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr != NULL)
	{
		//业务处理
		memset(ptr, 0, 100);
	}
	else
	{
		exit(-1);
	}
	//扩展容量
	//代码1
	//ptr = (int*)realloc(ptr, 1000);//这样不可以,如果申请失败原来的空间也找不到了
	//代码2						   //会导致内存泄露。
	int* p = NULL;
	p = (int*)realloc(ptr, 200);
	if (p != NULL)
	{
		ptr = p;
	}
	//业务处理
	memset(ptr + 25, 1, 100);
	free(ptr);
	return 0;
}

在这里插入图片描述

在这里插入图片描述

🦖常见动态内存错误

🐤对空指针的解引用操作

void test()
{
	int *p = (int *)malloc(INT_MAX/4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

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

void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if(NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for(i=0; i<=10; i++)
	{
		*(p+i) = i;//当i是10的时候越界访问
	}
	free(p);
}

🐤对非动态开辟内存使用free

void test()
{
	int a = 10;
	int *p = &a;
	free(p);//no
}

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

void test()
{
	int *p = (int *)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置,error
}

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

void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);//重复释放,对野指针free,error
}

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

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

有了这幅图,我们就可以很好的理解static关键字修饰局部变量的例子了。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

🦖柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

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

有些编译器会报错无法编译可以改成:

typedef struct st_type
{
	int i;
	int a[];//柔性数组成员
}type_a;

🐤柔性数组的特点

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

例如:

typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

在这里插入图片描述

🐤柔性数组的使用

//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
	p->a[i] = i;
}
free(p);

这样柔性数组成员a,相当于获得了100个整型元素的连续空间。

🐤柔性数组的优势

上述的 type_a 结构也可以设计为:

//代码2
typedef struct st_type
{
	int i;
	int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
	p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

第一个好处是:方便内存释放

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

第二个好处是:这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。

在这里分享一个扩展阅读链接里面更加详细的讲解的柔性数组:C语言结构体里的数组和指针

🦖结语

到这里这篇博客已经结束啦。
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀

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

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

相关文章

springMVC的响应

SpringMVC接收到请求和数据后&#xff0c;进行一些了的处理&#xff0c;当然这个处理可以是转发给Service&#xff0c;Service层再调用Dao层完成的&#xff0c;不管怎样&#xff0c;处理完以后&#xff0c;都需要将结果告知给用户。 对于响应&#xff0c;主要就包含两部分内容&…

关于 sensor hdr 模式下不出图/出图异常的排查方法

1、问题背景&#xff1a;有项目调试过 ov02k10&#xff08;1920*1080&#xff09;和 sc301IoT&#xff08;2048*1536&#xff09;两款 sensor, 都有出现 hdr 模式下出图异常或者不出图的问题&#xff0c;总结下排查过程及注意事项&#xff1b;2、问题现象&#xff1a;a、ov02k1…

Odoo 16 企业版手册 - 库存管理之寄售

寄售 使用“「设置」”菜单下提供的「寄售」选项&#xff0c;可以对库存中储存的产品设置所有者。产品将由零售商销售&#xff0c;但产品的实际所有权将由供应商持有&#xff0c;直到产品出售给客户。通过这种方法&#xff0c;您可以轻松地将未售出的产品退还给供应商。在寄售的…

java对接阿里云短信服务详解(验证码,推广短信,通知短信)

前言 小前提&#xff1a; - java&#xff1a;springboot框架&#xff0c;maven版本管理。 - 阿里云&#xff1a;有账号&#xff0c;已经进行实名认证。 java对接阿里云短信服务详解&#xff08;验证码&#xff0c;推广短信&#xff0c;通知短信&#xff09;前言1. 登录阿里云进…

基于servlet+mysql+jsp实现体育用品商城

基于servletmysqljsp实现体育用品商城一、系统介绍1、系统主要功能&#xff1a;2、环境配置二、功能展示1.主页(客户)2.登陆&#xff08;管理员&#xff09;3.主页&#xff08;管理员&#xff09;4.订单管理&#xff08;管理员&#xff09;5.客户管理&#xff08;管理员&#x…

linux系统结构

目录 0.前言 1.系统结构图 1.1.操作系统工作方式 1.2.高版本和低版本内核区别 0.前言 本专栏&#xff0c;是记录内核学习的&#xff0c;参考b站linux内核源码分析&#xff0c;以及linux内核艺术图解。后面的文章将记录个人的学习&#xff0c;源码注释&#xff0c;源码理解…

ANSYS Products 2020 R1 Linux64版本安装

fluent系列 占位 fluent2020R1版本安装fluent系列前言一、基础环境二、安装准备1.图形化环境准备2.路径准备3.挂载安装用iso4.拷贝安装文件三、开始安装1.进入图形化界面2.开始安装3.试运行fluent四、替换破解版的license总结前言 在centos7环境下安装使用fluent的部署记录。…

不用if else if 如何 解决文末尾问题

根据条件判断发送axios所携带的参数&#xff0c;这是搜索的2个条件&#xff0c;如果为空就按照空这个条件来搜索&#xff0c;所以为空携带参数就不能有他&#xff0c;导致if else if 的连续判断 开始来没有思路&#xff0c;随便尝试尝试&#xff0c;来打开自己的思路 期间尝…

【学习经验分享NO.20】代码报错(可帮助远程调试代码)

本博客会整理分享一些报错问题以及解决办法&#xff0c;本文会不断进行更新。有需求的朋友可以关注私信我&#x1f618;进行远程调试。&#x1f349;1.报错1问题nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.解决办法将项目中的F.sigmoid修改为torch.sigmo…

【docker16】Docker-Compose容器编排

1.是什么 Docker-Compose是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Compose是Docker公司推出的一个工具软件&#xff0c;可以管理多个Docker容器组成一个应用&#xff0c;你需要定义一个YAML格式的配置文件docker-compose.yml&#xff0c;写好…

JAVA导出Excel通用工具——第二篇:使用EasyExcel导出excel的多种情况的例子介绍

JAVA导出Excel通用工具——第二篇&#xff1a;使用EasyExcel导出excel的多种情况的例子介绍1. 前言2. 依赖3. 导出简单例子3.1 ① 基础入门例子3.1.1 核心代码3.1.2 效果展示3.2 ② 注解的简单使用3.2.1 ExcelIgnore3.2.2 ExcelProperty3.2.2.1 一般效果&#xff08;表头合并等…

MySQL高级【InnoDB引擎】

1&#xff1a;InnoDB引擎1.1&#xff1a;逻辑存储引擎 InnoDB的逻辑存储结构如下图所示: 1). 表空间 表空间是InnoDB存储引擎逻辑结构的最高层&#xff0c; 如果用户启用了参数 innodb_file_per_table(在 8.0版本中默认开启) &#xff0c;则每张表都会有一个表空间&#xff08…

【iOS】—— 初识block

block 文章目录block什么是block&#xff1f;block语法Block变量截获自动变量值__block说明符截获的自动变量block的三种存储类型NSGlobalBlockNSStackBlockNSMallocBlockblock的父类block循环引用未完待续什么是block&#xff1f; Blocks是带有自动变量&#xff08;局部变量&…

React--》初识React框架及其基本使用

目录 React React的安装与使用 JSX语法及使用 模块与组件 React开发者工具的安装 面向组件编程 React React是一个用于构建用户界面的JavaScript库。用户界面:HTML页面(前端)。React主要用来写HTML页面&#xff0c;或构建Web应用。 如果从 MVC的角度来看&#xff0c;…

第一天总结之后端登录功能的实现

第一天总结之后端登录功能的实现 一、 前端页面 从图片 很明显知道 两个intput输入框 一个输入username 一个输入password 从前端的页面代码 可以找到form表单 根据form表单的action属性了解到 点击登录跳转到 controller 层的 LoginServlet 二、controller 层 创建一个 Log…

2023年跨境电商新趋势,新手小白还有出路吗?

跨境电商一直位于我国对外开放的最前沿&#xff0c;当下已经成为我国进出口贸易的关键组成部分之一&#xff0c;是外贸企业顺利开展进出口业务的重要保障&#xff0c;更是拥有庞大发展潜力以及活力的贸易新业态。在经济全球化趋势下&#xff0c;充分发挥出跨境电商的战略新通道…

Java 包的使用详解

文章目录1. 概念2. 导入包中的类2.1 使用类的全路径2.2 导入包2.3 静态导入包3. 自定义包4. 包的访问权限控制5. 常用的包Java编程基础教程系列1. 概念 在开发过程中&#xff0c;会定义很多的类&#xff0c;随着类的定义越来越多&#xff0c;难免会出现类名重复的情况&#xf…

mac 安装redis

文章目录mac 安装redis使用Homebrew安装Redis1.搜索redis版本2.使用Homebrew安装命令3.查看是否安装完成4.启动redis服务5.查看redis服务进程6.redis-cli连接redis服务7.检测 redis 服务是否启动8.修改密码mac 安装redis 使用Homebrew安装Redis 首先这里需要安装homebrew 1.搜…

【Kubernetes 企业项目实战】03、基于 Alertmanager 发送报警到多个接收方(上)

目录 一、配置 Alertmanager 发送报警到 qq 邮箱 1.1 设置 163 邮箱 1.2 创建 alertmanager 配置文件 1.3 创建 prometheus 告警规则配置文件 1.4 安装 prometheus 和 alertmanager 1.5 部署 alertmanager 的 service 1.6 浏览器访问 Prometheus 和 alertmanager 二、配…

ELK日志(2)

elasticsearch群集状态颜色&#xff1a;灰色&#xff1a;未连接绿色&#xff1a;数据完整态黄色&#xff1a;副本不完整红色&#xff1a;数据分片不完整紫色&#xff1a;数据分片复制过程群集主机角色&#xff1a;主节点master&#xff1a;负责管理调度工作节点&#xff1a; 负…