动态内存分配/管理

news2024/11/28 14:41:17

目录

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

2、动态内存函数介绍

1、malloc

2、free

3、calloc

​编辑

4、realloc

 3、动态内存常见的错误

4、动态内存开辟相关好题

5、c/c++程序内存开辟示意图


int a,  int arr[10]  是固定地向内存申请连续的一块空间,但不能变长或变短随时调整。

在我们之前写的静态版通讯录中,我们创建了一个peopleinfo类型的数组date[100]用来存放100个人的个人信息,但是当我们的人员信息较小时,100个结构体显得有些浪费,而当我们所需存放的信息超过100时又不够用,随之我们就得修改这个数字,但是学了动态内存管理之后,我们可以动态地分配内存空间变大或变小,从而有效利用空间。

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

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时候就只能使用动态内存开辟了。

2、动态内存函数介绍

1、malloc

#include<stdlib.h>

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

先在堆区上动态申请一定空间,使用后应该还给操作系统,如果不主动还,程序结束后会自动还,但是如果程序一直不结束,就一直“占着不用”,就会造成空间的浪费。

2、free

用来释放、归还申请的内存

int main()
{
	//申请40个字节,用来存放10个整型
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//存放  1--10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p+i));
	}
	//free释放申请的内存
	free(p);

	return 0;
}

 

 用malloc申请后,内存中为随机值,使用时可给它们赋值,当free后,又变为随机值。

 仔细观察,我们可以发现,虽然这块空间的值发生了变化,但是指针p指向的地址free前后没有变化。因此,当我们free还给操作系统后,p仍指向这块空间。此时*p就会导致非法访问,因此我们需要将p制为NULL,避免非法访问。

	free(p);
	p = NULL;

总结:malloc申请空间后不会初始化,使用前要判断是否成功申请(是否返回NULL),使用后要free还给操作系统,然后将用于接收这块内存空间的指针p置为NULL。

 申请失败时返回NULL,并打印错误原因(没有足够空间)。

void  free(void * ptr)

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

3、calloc

 

由上图我们可以知道,malloc不会初始化,calloc会将每个元素先初始化为0,可以按需用。由于calloc需要初始化,效率比malloc稍低,此外没有其它区别,都需要进行相关步骤。

4、realloc

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = 1;
	}//空间不够,增加5个整型的空间
	//此时不能用p接收
	int* ptr = (int*)realloc(p, 10 * sizeof(int));
	//先用ptr接收,再赋给p,防止返回NULL,p找不到原来的数据
	if (ptr != NULL)
	{
		p = ptr;
        ptr = NULL;//释放但不置空,需手动置空
	}
	//继续使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}//realloc会把旧的空间释放,不用自己释放
	free(p);
	p = NULL;
}

 3、动态内存常见的错误

1、对NULL解引用(未判断是否为空)

2、非法访问内存,越界访问(解引用野指针)

3、对非动态开辟内存的空间用free释放

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

对栈区的空间进行释放(x)

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

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

5、对同一块内存多次释放

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

free一个NULL空指针时,什么事都不会发生。

malloc等函数是申请一块空间,获得使用它的权限,free释放后,将权限还给操作系统,如果再访问或是释放,就是非法访问。
6、动态开辟的内存忘记释放(内存泄漏)

malloc和free要成对出现,防止出现内存泄漏

int* test()
//函数内部进行了malloc操作,返回了malloc开辟的空间的起始地址
//谁接收了  要记得释放和置空
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		return 1;
	}
	return p;
}
int main()
{
	int* ptr = test();
	free(ptr);
	ptr = NULL;
}

4、动态内存开辟相关好题

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();
}

这里GetMemory函数是值传递,且没有返回值,对str无影响。因此str还是指向NULL,即0地址处,然后调用strcpy需访问0地址处内容,导致非法访问。

同时,上面用malloc申请了一块空间,但是函数内没有释放,函数销毁后,p也销毁,这块空间就会内存泄漏。记得malloc要与free搭配使用。

修改后,可传入&str,用char**p接收,*P=(char*)malloc(100),使p指向开辟的100byte,进而存放拷贝的内容,打印后free(str) str=NULL

或者将p(char*)直接返回,用str接收,因为没有free,所以malloc(100)仍然存在,进而可以strcpy,如果此时不是malloc申请,而是利用数组,函数销毁后,数组的空间也会释放,如果再进行打印就不行了。(如下图)这类问题简称为 返回栈空间地址的问题

可以在p前面加上static使其变为静态区变量,函数销毁后它不会销毁,或者去掉[],p变为char*类型,即从数组变为常量字符串,常量字符串也是在静态区,也就是把在栈区存储的数据放入静态区中存储,从而避免了返回栈空间地址的问题。

 printf(str)括号内直接加str是可以的,str是一个地址,例如printf(“hehe”),括号内有引号+字符串,也就是首字符的地址,相当于括号内直接加一个地址,最终结果都是打印字符串。

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

这段代码的整体思路没有问题,但是会导致内存泄漏,  malloc等函数需要与free共同使用

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

free(str)后已经没有权限访问了,但后面又调用strcpy访问空间,导致非法访问

开辟--释放--置空

习题来自《高质量C/C++编程》

5、c/c++程序内存开辟示意图

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

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

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

相关文章

快速理解机器学习、深度学习与自然语言处理

这篇文章对机器学习、深度学习、自然语言处理进行了简单的介绍&#xff0c;适合快速学习NLP与AI、ML和DL的关系。 机器学习、深度学习、自然语言处理的关系 机器学习、深度学习、自然语言处理的关系如图所示 1 机器学习&#xff08;Machine Learning&#xff09; 机器学习…

python中的设计模式:单例模式、工厂模式

目录 一.设计模式 二.单例模式 二.工厂模式 优点: 总结 一.设计模式 设计模式是一种编程套路&#xff0c;可以极大的方便程序的开发。 最常见、最经典的设计模式,就是我们所学习的面向对象了。 除了面向对象外,在编程中也有很多既定的套路可以方便开发&#xff0c;我们称…

《Linux Shell脚本攻略》学习笔记-第三章

3.1 简介 Unix为所有的设备和系统功能提供了文件形式的借口。可以通过这些特殊文件直接访问设备以及系统功能。 3.2 生成任意大小的文件 创建特定大小的文件最简单的方法就是利用dd命令。dd命令会克隆给定的输出内容&#xff0c;然后将一模一样的一份副本写入输出。 if表示输入…

个人总结:Mysql知识图谱

使用思维导图工具对mysql使用&#xff0c;进行知识总结。着重说下explanid SELECT识别符。这是SELECT查询序列号。这个不重要,查询序号即为sql语句执行的顺序select_type select类型table 输出的行所用的表&#xff0c;这个参数显而易见&#xff0c;容易理解partitions type 连…

Android系统启动(四) — Launcher 启动过程

1 Launcher 概述 系统启动的最后一步是启动一个应用程序来显示系统中已经安装的应用程序&#xff0c;这个应用程序就叫做 Launcher。Launcher 在启动过程中会请求 PackageManagerService 返回系统中已经安装的应用程序信息&#xff0c;并将这些信息封装成一个快捷图标列表显示…

阿里系-淘宝接口抓取及相关问题

阿里系-淘宝接口抓取 一、安装charlse抓包工具 官方下载地址 安装证书 二、安装xposed hook框架 Xponsed简介 具体安装步骤 三、安装模块 关闭阿里系ssl验证 开启http模式 支持支付宝、淘宝、淘宝直播各个接口抓取 四、效果如下 接下去一段时间更新阿里系相关接口 文章目录 一、…

搞技术的要不要学习财务知识

越是大型的集团或者企业&#xff0c;公司里面设立的部门就越多&#xff0c;也越细化&#xff0c;各部门之间既相互独立管理&#xff0c;又是相互的辅助支持&#xff0c;所以在工作中经常遇到这样的一个问题&#xff0c;就是做技术的要不要学习财务知识。这个问题其实就是把技术…

折半查找算法[二分查找法]算法的实现和解决整数溢出问题~

算法实现的要求&#xff1a; 折半查找法又称为二分查找法&#xff0c;这种方法对待查找的列表有两个要求&#xff1a; 1&#xff1a;必须采用顺序存储结构 2&#xff1a;必须按关键字大小有序排列算法思想&#xff1a; 将表中间位置记录的关键字与查找关键字进行比较&#x…

synchronized实现原理

0. 前言 造成线程安全问题的主要诱因有两点&#xff0c;一是存在共享数据(也称临界资源)&#xff0c;二是存在多个线程共同操作共享数据。因此为了解决线程安全问题&#xff0c;我们可能需要这样一个方案&#xff0c;当存在多个线程操作共享数据时&#xff0c;需要保证同一时刻…

ICV:2022年稀释制冷机全球市场规模达2.11亿美元,2028年有望出现突破点

全球前沿科技咨询机构ICV于2023年初发布了稀释制冷机&#xff08;DR&#xff09;的市场分析报告&#xff0c;ICV在报告中表示&#xff0c;2019-2015稀释制冷机的年均增长率达到8.59%以上&#xff0c;且增长率逐年上升。2022年全球稀释制冷机市场规模将达到2.11亿美元&#xff0…

从Deepmind最新成果DreamerV3启发的通用AI技术分析

一、背景 本文系个人观点&#xff1a;错漏在所难免&#xff0c;仅供参考 北京时间 1 月 12 日&#xff0c;DeepMind 官方推特发文&#xff0c;正式官宣 DreamerV3&#xff0c;这是首个能在游戏「我的世界」(Minecraft) 中不参考人类数据&#xff0c;就能从头开始收集钻石的通…

1.16中断实验

一.异常处理流程 1.异常处理流程 &#xff08;1&#xff09;保存现场&#xff08;CPU自动完成&#xff09; 将CPSR中状态保存到SPSR_<MODE>中 将CPSR寄存器的状态位T&#xff0c;改为ARM状态 根据需要&#xff0c;进制IRQ,FIQ中断&#xff0c;修改C…

java springboot 项目构建报错解决办法

这里总结了一些常见的springboot 项目构建报错及解决办法 错误: 无效的源发行版:17 错误原因 build.gradle 文件中可以通过下面两项来指定项目运行所需的jdk版本 sourceCompatibility:指定编译编译.java文件的jdk版本 targetCompatibility&#xff1a;确保class文件与target…

ARM 看门狗定时器

一、什么是看门狗、有什么用 (1) 看门狗定时器和普通的定时器并无本质区别。定时器可以设定一个时间&#xff0c;在这个时间完成之前定时器不断计时&#xff0c;时间到的时候&#xff0c;定时器会复位 CPU&#xff08;重启系统&#xff09;。 (2 )系统正常工作的时候当然不希望…

feign漫谈

feign的简单使用。 文章目录什么是feign准备工作三.如何使用3.1 定义pom文件3.2 定义配置文件及启动类注解3.3 定义feign接口什么是feign 远程调用框架 准备工作 需要nacos环境&#xff1a; 涉及到feign调用&#xff0c;就没法抛开注册中心&#xff0c;接下来我们使用主流的…

使用Python创建websocket服务端并给出不同客户端的请求

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 使用Python创建websocket服务端&#xff0c;并给出不同客户端的请求 一、 WebSocket是啥 WebSocket 和HTTP一样&#xff0c;也是一种通…

java中的位运算符

在Java语言中&#xff0c;提供了7种位运算符&#xff0c;分别是按位与&#xff08;&&#xff09;、按位或&#xff08;|&#xff09;、按位异或&#xff08;^&#xff09;、取反(~)、左移(<<)、带符号右移(>>)和无符号右移(>>>)。位运算符是对long、i…

怎么把两个PDF合并成一个?这几种操作轻松合并

我们在工作中处理过的文件有很多&#xff0c;有时候为了工作更方便&#xff0c;我们通常需要把两个或是多个文件合并成为一个PDF文件&#xff0c;这样只需要打开这一个文件就可以查看全部内容&#xff0c;那么怎么把两个PDF合并成一个呢&#xff1f;这几种操作都可以轻松合并&a…

Dopamine-PEG-NH2氨基聚乙二醇多巴胺,材料改性用科研试剂

英 文 &#xff1a;NH2-PEG-Dopamine/Dopamine-PEG-NH2 中文&#xff1a;氨基聚乙二醇多巴胺 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff1a;仅供科研实验使用&#xff0c;不用于诊治 外观: 固体或粘性液体&#xff0c;取决于分子量 注意事项…

javaEE 初阶 — 文件内容的读写

文章目录数据流1. 字节流1.1 InputStream 概述1.1.1 无参数 read 的使用1.1.2 一个参数 read 的使用1.2 使用 OutputStream 写文件1.2.1 对于关闭文件的解释2. 字符流2.1 Reader 概述2.1.1 read 方法的使用2.2 Writer 概述2.2.1 write 的使用2.3 Scanner 补充数据流 针对文件内…