c语言中动态内存管理

说到内存,大家一定都知道。但是有一种函数可以实现动态内存管理,下面大家一起学习。

文章目录

  • 一、为什么要有动态内存管理?
  • 二、malloc 和 free
    • 1.malloc
    • 2.free
  • 三、calloc 和 realloc
    • 1.calloc
    • 2.realloc
    • 3.常见的动态内存的错误
      • 3.1对NULL指针的解引用错误
      • 3.2对动态开辟空间的越界访问
      • 3.3对非动态开普内存使用free释放
      • 3.4 使用free释放一块动态开辟内存的一部分。
      • 3.5 对同一块动态内存多次释放。
      • 3.5 动态开辟内存忘记释放(内存泄漏)
    • 4.经典笔试题分析
      • 4.1
      • 4.2
      • 4.3
      • 4.4
  • 四、柔性数组
    • 1.柔性数组的特点
    • 2.柔性数组的使用


一、为什么要有动态内存管理?

经过c语言的学习,相信大家都知道如何开辟一块内存。

int i = 6;
int arr[10]={0};

上面代码开辟的空间,有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候必须指定数组的长度,数组空间一旦确定大小就不能调整了。

这时候我们会发现当我们写代码的时候,我们需要开辟多少空间在程序运行的时候才能知道。这时候就需要引入动态内存开辟了,让程序员自己申请和释放空间。

二、malloc 和 free

1.malloc

void* malloc (size_t size);

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

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

在这里插入图片描述
这个代码用了malloc函数申请空间,先是申请了20个字节的空间强制转换为整形变为存放5个整数。

2.free

 void free (void* ptr);

函数free,是专门用来给动态内存的释放和回收的。

  1. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为毫无意义。
  2. 如果参数ptr是NULL指针,则函数什么事都不做。
#include <stdio.h>
#include <stdlib.h>int main(){int num = 0;scanf("%d", &num);int arr[num] = {0};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;//是否有必要?return 0;}

需要注意的是,使用free函数释放内存后,我们不能再访问该内存块,否则会导致未定义的行为。此外,如果我们尝试释放已经释放过的内存块,或者释放非动态分配的内存块,也会导致未定义的行为。当动态内存使用完毕之后,用free释放,释放后的指针是野指针,记得置空。

三、calloc 和 realloc

1.calloc

void* calloc (size_t num, size_t size);

calloc函数的功能是为num个大小为size的元素开辟一块空间,并把空间的每个字节置为0。
calloc函数与malloc函数的区别只在于calloc会在地址返回之前把申请的空间全部置为0。
在这里插入图片描述
输出结果全为 0;
在这里插入图片描述
当我们需要对申请的内存空间的内容进行初始化的时候,calloc函数就很方便的解决了这一问题。

2.realloc

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

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

  1. ptr是要调整的内存地址。

  2. size是调整之后新的大小。

  3. 返回值为调整之后的内存起始位置。

  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

  5. realloc函数调整内存的是存在两种情况:

    	 1. 原有空间之后有足够大的空间。2. 原有空间之后没有足够大的空间。
    

在这里插入图片描述

在这里插入图片描述
这种情况当原有空间后面内存正好可以放下我们扩展的内存的时候。扩展内存就直接再原有内存之后直接追加空间,原有空间数据不发生变化。
在这里插入图片描述
这种情况当原有空间之后没有足够多的空间时,扩展方法时:在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址。

3.常见的动态内存的错误

3.1对NULL指针的解引用错误

 void test(){int *p = (int *)malloc(INT_MAX/4);*p = 20;free(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;}free(p);}

当i是10的时候,指针已经越界访问了。

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

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

要知道栈上的内存是不需要程序员手动释放的,只有堆上的内存需要程序员手动释放。

释放堆上内存的主要原因:

  1. 避免内存泄漏:如果我们不释放动态分配的内存,那么这块内存将一直被程序占用,无法被其他部分使用。如果重复分配内存而不释放,最终会导致内存耗尽,程序崩溃或运行缓慢。
  2. 释放不再使用的内存:在程序执行过程中,可能会动态地分配和释放内存。当我们不再需要某个内存块时,通过释放它,可以将其返回给操作系统,以便其他程序可以使用这块内存。
  3. 防止悬空指针:如果我们释放了一块内存,但仍然保留了指向该内存的指针,那么这个指针就成为了悬空指针。使用悬空指针可能导致未定义的行为,例如访问无效的内存,引发程序崩溃或产生不可预测的结果。通过释放内存并将指针置为,可以避免悬空指针的问题。
  4. 提高内存利用率:释放不再使用的内存可以提高内存的利用率。这对于内存有限的嵌入式系统或需要处理大量数据的应用程序尤为重要。

不需要释放栈上内存的原因:

1. 自动管理:栈上的内存分配和释放是由编译器自动完成的,程序员不需要手动干预。当函数执行完毕或者局部变量超出作用域时,编译器会自动将其所占用的内存空间释放。
2. 先进后出:栈是一种后进先出(LIFO)的数据结构,栈上的内存分配和释放遵循这个原则。每次函数调用时,会将局部变量和参数压入栈中,当函数返回时,会将这些变量从栈中弹出,实现内存的自动释放。
3. 快速高效:由于栈上的内存分配和释放是由编译器自动完成的,所以速度非常快。相比于堆上的内存分配和释放,栈上的内存管理更加高效。

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

 void test(){int *p = (int *)malloc(100);p++;}free(p);

这个代码p指针发生变化,p指针不在指向这块内存的起始位置,那么最后free只能释放一部分内存造成内存泄漏。

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

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

对同一块动态内存多次释放可能会造成内存泄漏、悬空指针、程序崩溃。

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

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

忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间⼀定要释放,并且正确释放。

4.经典笔试题分析

4.1

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

在GetMemory函数中,用malloc函数为p重新分配了内存,但是这个分配的内存并没有被传回函数中,因为在main函数中函数参数是按值传递的,所以在GetMemory函数中重新分配内存并不会改变str指针在main函数中的值。
在main函数中,str指针被初始化为NULL传递给GetMemory函数。由于GetMemory函数中的参数是按值传递的,所以GetMemory函数内部对p指针的修改不会影响到str指针。所以str指针仍然是NULL指针,在后面的strcpy和printf中会导致对空指针的解引用,产生未定义的内容。

4.2

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

这个代码中GetMemory函数是创建在栈上的,里面的char数组也是栈空间。随着函数的释放,返回到main函数的虽然是这个数组的首地址,但是str已经无权访问了变成了空指针。虽然有可能代码最后会输出hello word ,那是因为这个空间还没有被系统的其他空间所覆盖。

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

传址调用,并且 GetMemory函数用二级指针**p接收。在GetMemory函数释放空间的时候,成功的传回了动态内存的首地址使得str不在是空指针,可以进行strcpy和printf。但是没有释放p。

4.4

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

过早释放动态,没有了对动态内存空间的访问权限。后面的world无法拷贝。

四、柔性数组

C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。

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

1.柔性数组的特点

1.结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
2.sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
3.包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

2.柔性数组的使用

struct S
{int n;//4int arr[];
};int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 5*sizeof(int));if (ps == NULL){perror("malloc");return 1;}ps->n = 100;int i = 0;for (i = 0; i < 5; i++){ps->arr[i] = i;}//调整空间struct S* ptr = (struct S*)realloc(ps, sizeof(struct S)+10*sizeof(int));if (ptr != NULL){ps = ptr;}//....//释放free(ps);ps = NULL;return 0;
}

到这里动态内存管理基本就结束了,大家如果有问题可以一起讨论。谢谢大家!

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

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

相关文章

c++ 有名对象和匿名对象

c 有名对象和匿名对象 有名对象就是有名字的对象&#xff0c;匿名对象就是没有名字的对象。 #define _CRT_SECURE_NO_WARNINGS 1 using namespace std; #include<iostream> class score { public:score(){math 100;chinese 100;english 100;}score(int _math, int _…

Java Web-Tomcat

Web服务器 Web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”。 Tomcat&#xff0c;是一个 HTTP 服务器。我们只需要在服务器中安装一个Web服务器如Tomcat&#xff0c;然后就可以将…

数据安全之路:Databend 用户策略指南

在 Databend 中&#xff0c;我们致力于保护用户的数据安全。除了身份认证之外&#xff0c;我们还提供了多种访问策略&#xff0c;包括网络策略&#xff08;Network Policy&#xff09;、密码策略&#xff08;Password Policy&#xff09;和数据脱敏策略&#xff08;Masking Pol…

JavaScript进阶5之垃圾回收(计算机组成、解释与编译、JavaScript引擎、垃圾回收、内存管理)、运行机制(浏览器进程分类、浏览器事件循环)

垃圾回收&运行机制 垃圾回收计算机组成解释与编译JavaScript引擎V8引擎 垃圾回收引用计数法标记清除&#xff08;mark-sweep&#xff09;算法 内存管理新生代 运行机制浏览器进程分类&#xff1a;浏览器事件循环宏任务微任务整体流程浏览器事件循环案例一案例二 垃圾回收 …

Unity学习日记 11.单词识别游戏

目录 1.返回鼠标单击对象的名字 2.鼠标拖动移动对象 3.实现鼠标跟随 4.场景准备工作 5.判断图片与框配对 6.根据配对结果放置图片 1.返回鼠标单击对象的名字 步骤&#xff1a; 创建一个ShowName的脚本&#xff0c;并挂载在摄像机上 RaycastHit2D hitInfo;void Update(){…

CANalyzer使用_04 使用CAN报文发送数据

本文手把手介绍使用CAN来发送数据。分为创建工程&#xff0c;创建CAN报文&#xff0c;运行效果&#xff0c;参考文献。 1 创建工程 双击“CANalyzer->单击“I accept”->等一会等软件打开后&#xff0c;单击“File”->单击"New"->双击"CAN 500kBa…

vue3+ts+element home页面侧边栏+头部组件+路由组件组合页面教程

文章目录 效果展示template代码script代码样式代码 效果展示 template代码 <template><el-container class"home"><el-aside class"flex" :style"{ width: asideDisplay ? 70px : 290px }"><div class"aside-left&q…

json文件美化工具(json tools)

自动整理json文件&#xff0c;使用&#xff1a;ctrlaltM

【数学】第十三届蓝桥杯省赛C++ A组/研究生组 Python A组/研究生组《数的拆分》(C++)

【题目描述】 给定 T 个正整数 &#xff0c;分别问每个 能否表示为 的形式&#xff0c;其中 , 为正整数&#xff0c;, 为大于等于 2 的正整数。 【输入格式】 输入第一行包含一个整数 T 表示询问次数。 接下来 T 行&#xff0c;每行包含一个正整数 。 【输出格式】 对于…

浅析JS原型链

目录 实例对象原型对象对象原型短暂总结一下constructor原型链 何为原型链呢&#xff1f; 就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构.称为原型链。 这里又扯到了另外两个概念了。 实例对象>>&g…

PyTorch 教程-快速上手指南

文章目录 PyTorch Quickstart1.处理数据2.创建模型3.优化模型参数4.保存模型5.加载模型 PyTorch 基础入门1.Tensors1.1初始化张量1.2张量的属性1.3张量运算1.3.1张量的索引和切片1.3.2张量的连接1.3.3算术运算1.3.4单元素张量转变为Python数值 1.4Tensor与NumPy的桥接1.4.1Tens…

腾讯云轻量4核8G12M服务器配置4C8G12M详解

4核8G是云服务器的参数&#xff0c;代表云服务器的硬件配置和网络带宽&#xff0c;4核代表CPU、8G是指内存、12M代表带宽值为12Mbps&#xff0c;腾讯云百科txybk.com以腾讯云轻量应用服务器4核8G12M带宽配置为例&#xff0c;来详细介绍下服务器参数&#xff1a; 4c8g是什么意思…

Unity学习笔记 9.2D射线

下载源码 UnityPackage 1.Ray2D 让小球向右发射射线&#xff1a; Ray2D ray;void Start() {// Ray2D(起点&#xff0c;终点)ray new Ray2D(this.transform.position, Vector2.right);// Debug.DrawLine(起点&#xff0c;终点&#xff0c;颜色&#xff0c;显示时间)Debug.DrawL…

嵌入式开发——基础电路知识

1. 电路知识 1.1. 驱动能力 IC是数字逻辑芯片&#xff0c;其输出的是逻辑电平。逻辑电平0表示输出电压低于阈值电压&#xff0c;逻辑1表示输出电压高于阈值电压。负载则是被驱动的电路或元件&#xff0c;负载大小则指负载的电阻大小。 驱动能力主要表现在几个方面&#xff1…

MySQL使用教程:数据库、表操作

目录 1. 免密码登录MySQL1.1 免密码配置1.2 登录选项介绍 2. MySQL基础配置&#xff1a;my.cnf3. 开机自启动设置&#xff08;可选设置&#xff09;4. 查看存储引擎5. 查看系统的编码规则和校验规则6. 数据库的操作6.1 查看数据库6.2 创建数据库 create database6.3 删除数据库…

【[NOIP1999 普及组] Cantor 表】

题目描述 现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&#xff1a; 我们以 Z 字形给上表的每一项编号。第一项是 1 / 1 1/1 1/1&#xff0c;然后是 1 / 2 1/2 1/2&#xff0c; 2 / 1 2/1 2/1&#xff0c; 3 / 1 3/1…

腾讯云2核4G服务器最大能承载多少人访问?

腾讯云轻量应用服务器2核4G5M配置性能测评&#xff0c;腾讯云轻量2核4G5M带宽服务器支持多少人在线访问&#xff1f;并发数10&#xff0c;支持每天5000IP人数访问&#xff0c;腾讯云百科txybk.com整理2核4G服务器支持多少人同时在线&#xff1f;并发数测试、CPU性能、内存性能、…

python入门题:输入输出练习

以下是Python基础语法的练习&#xff0c;项目要求和代码如下&#xff1a; """ 例3&#xff1a;小精灵&#xff1a;你好&#xff0c;欢迎古灵阁&#xff0c;请问您需要帮助吗&#xff1f;需要or不需要&#xff1f; 你&#xff1a;需要 小精灵&#xff1a;请问你需…

图片照片怎么做成二维码?图片快速生成二维码的简单教学

随着互联网的快速发展&#xff0c;二维码作为现在很常用的一种内容载体方式&#xff0c;可以应用的场景越来越多&#xff0c;不管是用于企业宣传、产品介绍、问卷调查等类型的内容&#xff0c;都能够使用二维码的形式展示。二维码能够提供更加快捷的有效的推广效果&#xff0c;…

高阶数据结构 <红黑树>

本文已收录至《数据结构(C/C语言)》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言正文红黑树简介红黑树整体结构红黑树节点的定义红黑树主体类设计红黑树的插入函数情况一&#xff1a;变色情况二&#xff1a;变色旋转单旋情况双旋情况 完整插入代码 关于红黑树红黑树检…