动态内存分配及管理——C语言

目录

一、为什么存在动态内存分配

二、动态内存函数介绍

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

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


        今天,博主给大家带来的是动态内存分配的学习和讲解。在之前,我们学习了通讯录,文章中利用到一些动态内存分配的一些知识,有些可能大家会看不懂,那么相信通过今天的这篇文章,大家的问题就会迎刃而解。本篇,我们将从“为什么存在内存分配”,“动态内存函数介绍”,以及“常见的动态内存错误”三个板块来为大家一 一解答。

一、为什么存在动态内存分配

在之前,我们学过的内存开辟有哪些呢?比如,创建一个变量,或者创建一个数组。

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

上面两种开辟方式是我们常用的开辟内存的方式,但是这两种开辟内存空间的方式有两个特点:

① 空间开辟大小是固定的

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

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

二、动态内存函数介绍

在学习动态内存函数之前,我们需要知道动态内存开辟的空间是放在堆区的,如上图所示,局部变量和形式参数占用的空间是在栈区的,全局变量以及静态变量开辟的空间是在静态区的。 

2.1 malloc

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

void* malloc  (   size_t    size   ) ;
这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
① 如果开辟成功,则返回一个指向开辟好空间的指针。
② 如果开辟失败,则返回一个NULL指针,因此 malloc 的返回值一定要做检查。
③ 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
④ 如果参数 size 为 0 malloc 的行为是标准是未定义的,取决于编译器。
举个代码例子来解释吧!
int main()
{
	int* p = (int*)malloc(40);//开辟40个字节的空间
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}

	return 0;
}
因为返回的类型是void*,所以我们要根据自己的需求来进行强制类型转换,其次,我们需要判断是否开辟成功,如果返回值为NULL指针,那么就结束了,反之则是开辟成功。然后我们打印一下看看开辟成功的空间里面是什么。

此时我们发现开辟的空间里面存的是一堆没见过的随机数数,其实是malloc函数申请的空间,在申请成功后,直接返回这片空间的起始地址,不会初始化空间的内容。 

2.2 free

C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:
void free void*   ptr  ) ;
上面我们学习了malloc函数,我们发现,malloc只负责申请空间,那么申请的这个空间当我们使用完之后会怎么样呢?其实这块空间并不会主动的还给操作系统,除非程序结束,否则这块空间将会一直存在堆区。这个时候就需要另一个内存函数了。
总的来说: malloc申请的内存空间,当程序退出时,还给操作系统,当程序不退出时,动态申请的内存不会主动释放。需要free函数来释放空间。
具体怎么使用呢,直接把我们动态开辟的空间的起始地址传入free函数中即可,代码如下图所示:
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	free(p); //释放开辟的空间
	p = NULL;  //置空
	return 0;
}

注意:p本来指向的空间被释放后,p就变成野指针了,比较危险,这时候我们需要主动将p置为空指针。

free只能释放动态开辟的空间,不能释放静态区或者栈区开辟的空间(标准未定义) 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

② 如果参数 ptr NULL 指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中

2.3 calloc

C 语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。原型如下:
void*   calloc size_t   num  ,   size_t   size  ) ;
① 函数的功能是为 num 个大小为 size 的元素开辟一块空间 ,并且把空间的每个字节初始化为 0
② 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
举个例子吧:
int main()
{
	int* p = (int*)calloc(10, sizeof(int));//开辟十个连续的sizeof(int)大小的空间
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//打印数据
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

当我们打印完之后,发现calloc会把每个字节初始化为0。

总的来说:calloc函数和malloc函数很相似,功能也是一样,唯一不同的就是,会把开辟的每个字节都初始化为0。

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

2.4 realloc

realloc 函数的出现让动态内存管理更加灵活。
        有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void*   realloc  void*   ptr  ,   size_t   size  ) ;
① ptr 是要调整的内存地址(也就是被调整空间的起始地址,这块空间之前已经开辟好了)
如果ptr为空指针,那么它的功能和malloc就是一样的,开辟一个新的空间。
② size 调整之后新大小 (需要调整的新的空间的大小)
③ 返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
④ realloc 在调整内存空间的是存在两种情况:
情况1 :原有空间之后有足够大的空间
情况2: 原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,同时把原来空间的内容拷贝过来,然后自动释放原来的内存空间,这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些,我们这里用代码来举例子吧:
 
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//初始化为1~10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	//增加一些空间
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL)  //开辟成功
	{
		p = ptr;
		ptr = NULL;
	}
	else  //开辟失败
	{
		perror("realloc");
		return 1;
	}
	//开辟成功,打印数据
	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);  //释放空间
	p = NULL;  //置空
	return 0;
}

注意:考虑到可能开辟失败,所以我们需要先进行判断一下,如果开辟成功,则将返回的指针赋值给p来维护。

当我们开辟完之后,打印一下看看效果。

当我们使用完动态开辟的内存之后,仍然需要手动去释放内存空间。

 当然,上面情况只是减少增加空间,如果要减少空间就比较简单了,直接在原来的基础上减少,地址返回的也是原来的地址。

好了,到这里动态内存管理的基本知识就介绍清楚了,实际上把这四个内存函数了解清楚,基本上对动态内存的知识点也就基本掌握了。接下来,我们来了解一下动态内存管理常见的内存错误,通过解释这些错误,来更清楚更深入的了解动态内存管理。

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

当我们动态内存开辟的时候,会存在开辟失败的情况,此时返回的就是空指针。

如下代码就是一个典型的例子:

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

此时动态内存开辟可能失败了,就导致返回的指针为空指针,也就是p为空指针,如果再对p这个空指针进行解引用操作,那么就会报错 。为了解决这种问题,我们在平时写代码的时候,为了避免空指针异常,要对动态开辟的空间返回的指针进行空指针判断检查。(好习惯)

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

这里直接拿代码来解释吧!

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

这段代码中,我们使用malloc开辟40个字节大小的空间,然后进行空指针检查判断,紧接着,在我们赋值的时候,我们最多只能访问到p[9]这块空间,代码中我们i的最大值为10,此时很明显,就出现了越界访问。

总的来说:开辟多少空间,就只能使用多少空间,没有开辟的,属于操作系统的空间,我们不可以随便进行操作访问。

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

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

在前面讲free的时候说过,free释放的空间,只能是动态内存开辟的空间,不能是静态区或者栈区开辟的空间,上面代码的例子中,a是局部变量,不是动态开辟的空间,所以不能被free释放。

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

int main()
{

	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i + 1;
		p++;  //此时p不再指向malloc开辟的空间的起始地址
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

上面代码中,我们使用malloc开辟一块空间,然后将起始地址返回给p,也就是说p指向molloc动态开辟的空间的起始地址,紧接着进行空指针检查判断,然后给p指向的空间进行赋值,但是,在赋值的过程中,p的指向发生了变化(如下图),不再指向malloc开辟的空间的起始地址,此时在对p指向的空间进行释放,这种做法是不被允许的,会报错。

总结:free中的参数只能是动态内存开辟空间的起始地址,对于动态开辟的空间的起始地址不要随便的更改指向。

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

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

上面这种情况也是一直很低级的错误,也是不被允许的,会报错,最好的解决办法就是,在释放完之后,将p指针置为空,这样在后面多次释放也不影响,因为p已经是空指针了。

总结:不能对同一块动态内存多次释放,解决办法:在第一次释放完之后,将指针置为空指针(好习惯)

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

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

对于上面这个代码,在test这个函数种,我们开辟了100个字节的动态空间,但是在最后,我们并没有对这块空间进行free释放,这时候当我们跳出函数之后,指针变量p也销毁了,这个时候,p原来指向的空间我们根本找不到了,但是这块空间仍然存在,仍然被占用,只是我们丢失了它的起始地址,不能再对这块空间进行操作或者访问了,这就造成了这块空间仍然存在占用内存,但是我们却访问不到,并无法释放,这就是所谓的内存泄露。

针对这个问题的解决:在我们使用完动态空间之后,一定要进行free释放。

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

好了,今天的动态内存分配和管理讲到这里就结束了,听到这里,相信大家的一些关于动态内存分配管理的问题就会迎刃而解了吧。如果哪里有问题,欢迎在评论区留言。如果觉得小编写的还不错的,那么可以一键三连哦,您的关注点赞和收藏是对小编最大的鼓励。谢谢大家!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3 Vuex状态管理多组件传递数据简单应用

去官网学习→安装 | Vuex cd 项目 安装 Vuex&#xff1a; npm install --save vuex 或着 创建项目时勾选Vuex vue create vue-demo ? Please pick a preset: Manually select features ? Check the features needed for your project: (Press <space> to se…

Web3 solidity订单池操作

前面一篇文章因为一些原因 被设为了进自己可见 需要的朋友可以私信我 之前 我们编写的程序上来看 交易所无非是一个代币的托管上 只是它会更加专业 本文 我们继续来看交易所的一个功能 叫游泳池 例如 我们 100grToken 兑换 1ETH 前提 我们的代币已经能被估值了 例如 你想用人…

【JVM】如何判定一个对象已死以及“标记-清除”、“标记-复制”、“标记-整理”三种垃圾收集算法

文章目录 0、如何判定一个对象的生死&#xff1f;1、上文提到的引用又是什么1、强引用&#xff1a;2、软引用&#xff1a;3、弱引用&#xff1a;4、虚引用&#xff1a; 2、垃圾收集算法1、标记-清除2、标记-复制优化&#xff1a;&#x1f447; 3、标记-整理 0、如何判定一个对象…

item_password-获得淘口令真实url

一、接口参数说明&#xff1a; item_password-获得淘口令真实url &#xff0c;点击更多API调试&#xff0c;请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_password 名称类型必须描述keyString是调用key&#xff08…

用cpolar生成的公网地址,对位于本地的Cloudreve网盘进行访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

Springboot集成ip2region离线IP地名映射-修订版

title: Springboot集成ip2region离线IP地名映射 date: 2020-12-16 11:15:34 categories: springboot description: Springboot集成ip2region离线IP地名映射 1. 背景2. 集成 2.1. 步骤2.2. 样例2.3. 响应实例DataBlock2.4. 响应实例RegionAddress 3. 打开浏览器4. 源码地址&…

米尔瑞萨RZ/G2L开发板-02 ffmpeg的使用和RTMP直播

最近不知道是不是熬夜太多&#xff0c;然后记忆力减退了&#xff1f; 因为板子回来以后我就迫不及待的试了一下板子&#xff0c;然后发现板子有SSH&#xff0c;但是并没有ffmpeg&#xff0c;最近总是在玩&#xff0c;然后今天说是把板子还原一下哇&#xff0c;然后把官方的固件…

Beats:安装及配置 Metricbeat (一)- 8.x

在我之前的文章&#xff1a; Beats&#xff1a;Beats 入门教程 &#xff08;一&#xff09;Beats&#xff1a;Beats 入门教程 &#xff08;二&#xff09; 我详细描述了如何在 Elastic Stack 7.x 安装及配置 Beats。在那里的安装&#xff0c;它通常不带有安全及 Elasticsearc…

Redis - 数据类型映射底层结构

简介 从数据类型上体现就是&#xff0c;同一个数据类型&#xff0c;在不同的情况下会使用不同的编码类型&#xff0c;底层所使用的的数据结构也不相同。 字符串对象 字符串对象的编码可以是 int、raw 和 embstr 三者之一。 embstr 编码是专门用于保存简短字符串的一种优化编…

Docker查看、创建、进入容器相关的命令

1.查看、创建、进入容器的指令 用-it指令创建出来的容器&#xff0c;创建完成之后会立马进入容器。退出之后立马关闭容器。 docker run -it --namec1 centos:7 /bin/bash退出容器&#xff1a; exit查看现在正在运行的容器命令&#xff1a; docker ps查看历史容器&#xff0…

解决Java中的“Unchecked cast: java.lang.Object to java.util.List”问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

嵌入式系统中如何选择RTC电池?

RTC&#xff08;Real Time Clock&#xff09;是一种用于提供系统时间的独立定时器&#xff0c;它可以在系统断电或低功耗模式下继续运行&#xff0c;只需要一个后备电池作为供电源。在嵌入式系统中&#xff0c;选择合适的RTC电池时非常关键的&#xff0c;它会影响系统时间的准确…

CSS自己实现一个步骤条

前言 步骤条是一种用于引导用户按照特定流程完成任务的导航条&#xff0c;在各种分步表单交互场景中广泛应用。例如&#xff1a;在HIS系统-门诊医生站中的接诊场景中&#xff0c;我们就可以使用步骤条来实现。她的执行步骤分别是&#xff1a;门诊病历>遗嘱录入>完成接诊…

【设计模式】装饰器模式

装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。这种类型的设计模式属于结构型模式&#xff0c;它是作为现有的类的一个包装。 装饰器模式通过将对象包装在装饰器类中&#xff0c;以便动态地修改其行为…

Gitlab CI/CD笔记-第二天-主机套接字进行构建并push镜像。

一、安装gitlab-runner 1.可以是linux也可以是docker的 2.本文说的是docker安装部署的。 二、直接上.gitlab-ci.yml stages: # List of stages for jobs, and their order of execution - build-image build-image-job: stage: build-image image: harbor.com:543/docke…

【Java转Go】快速上手学习笔记(三)之基础篇二

【Java转Go】快速上手学习笔记&#xff08;二&#xff09;之基础篇一 了解了基本语法、基本数据类型这些使用&#xff0c;接下来我们来讲数组、切片、值传递、引用传递、指针类型、函数、map、结构体。 目录 数组和切片值传递、引用传递指针类型defer延迟执行函数map结构体匿名…

【仿写框架之仿写Tomact】四、封装HttpRequest对象(属性映射http请求报文)、HttpResponse对象(属性映射http响应报文)

文章目录 1、创建HttpRequest对象2、创建HttpResponse对象 1、创建HttpRequest对象 HttpRequest对象中的属性与HTTP协议中的内容对应&#xff0c;用于后序servlet从request中获取请求中的参数。 参照http请求报文&#xff1a; import java.io.BufferedReader; import java…

2023年国赛数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 最短时…

FreeModbus——介绍(二)

1.简介 freemodbus_百度百科 (baidu.com) &#xff1a;参考自百度百科&#xff0c;里面还有移植介绍&#xff0c;非常详细 1. FreeMODBUS是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费&#xff08;自由&#xff09;的通用MODBUS协议的移植。 2. FreeMOD…

【Linux】模拟实现linux的shell

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/types.h> #define NUM 1024 #define SIZE 32 #define SEP " " int main() {//保存输入后的字符串char …