动态内存管理学习分享

动态内存管理学习分享

  • 1. 为什么存在动态内存分配
  • 2. 动态内存函数的介绍
    • 2.1 [malloc](https://legacy.cplusplus.com/reference/cstdlib/malloc/?kw=malloc)和[free](https://legacy.cplusplus.com/reference/cstdlib/free/?kw=free)
      • 2.1.1 实例
    • 2.2 [calloc](https://legacy.cplusplus.com/reference/cstdlib/calloc/?kw=calloc)
      • 2.2.1 实例
    • 2.3 [realloc](https://legacy.cplusplus.com/reference/cstdlib/realloc/?kw=realloc)
  • 3. 常见的动态内存错误
    • 3.1 对NULL指针的解引用操作
    • 3.2 对动态开辟空间的越界访问
    • 3.3 对非动态开辟内存使用free释放
    • 3.4 使用free释放一块动态开辟内存的一部分
    • 3.5 对一块动态内存进行多次释放
    • 3.6 开辟动态内存忘记释放([内存泄漏](http://t.csdn.cn/x2vbA))
  • 4 C/C++程序的内存开辟
  • 5 柔性数组
    • 5.1 柔性数组的特点
    • 5.2 柔性数组的使用
    • 5.3 柔性数组的优势
  • 6. 结尾


在这里插入图片描述


1. 为什么存在动态内存分配

我们常见的内存开辟方式有:

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

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

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

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


2. 动态内存函数的介绍

2.1 malloc和free

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

void* malloc (size_t size);

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

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。

  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

  3. 返回值的类型是void*,所以malloc函数并不知道开辟内存空间的类型,具体在使用的时候使用者自己来决定。

  4. 如果参数size为0,malloc的行为是标准未定义的,取决于编译器。

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

void free (void* ptr);

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

malloc和free都声明在stdlib.h头文件中。
Tips:

  • malloc函数申请到空间后,直接返回这块空间的起始地址,并不会初始化空间的内容。
  • malloc函数申请的内存空间,当程序退出时会还给操作系统。但当程序没有退出时,动态申请的空间不会主动释放。需要使用free函数来释放。

2.1.1 实例

#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(40);if (p == NULL)//检查是否开辟成功{perror("malloc");//打印错误码信息return 1;}//内存申请开辟成功for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}//释放回收申请空间free(p);p = NULL;return 0;
}

运行结果:
在这里插入图片描述


2.2 calloc

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

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

2.2.1 实例

int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}for (int i = 0; i < 10; i++){printf("%d:%p\n", p[i],&p[i]);}return 0;
}

结果:

在这里插入图片描述


2.3 realloc

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

void* realloc (void* ptr, size_t size);
  • ptr是要调整的内存地址,size为调整后的新大小。
  • 返回值为调整之后的内存起始位置。
  • realloc在调整内存空间时会存在两种情况:
    在原有空间之后有足够大小的空间要扩展内存之后直接追加空间,原来空间的数据不发生变化。
    原有空间之后没有足够大的空间在栈空间上另找一个合适大小的连续空间重新开辟一块内存空间,并将旧空间中的数据拷贝到新的空间,并释放旧空间,返回新空间的起始位置。

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{int* p = (int*)malloc(40);//没有进行判断是否开辟成功//若开辟失败,则p为空指针。//对空指针进行解引用操作是非法的*p = 20;free(p);return 0;
}

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

int main()
{int* p = (int*)malloc(40);//开辟10字节空间if (p == NULL){perror("malloc");return 1;}for (int i = 0; i <= 10; i++)//越界访问{*(p + i) = i + 1;}free(p);p = NULL;return 0;
}

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

int main()
{int a = 10;int* p = &a;free(p);//errreturn 0;
}

后果
对非动态开辟内存释放会导致内存泄漏,如果内存泄漏发生在长时间运行的程序中,可能会导致系统资源耗尽,使整个系统变得不稳定。


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

int main()//err
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}for (int i = 0; i < 10; i++){*p = i + 1;p++;//p在不断向后移动}//释放free(p);//释放时,只是放了一小部分p = NULL;return 0;
}

后果
释放部分内存可能会导致内存损坏,因为该内存可能被后续的内存分配操作重叠使用。这可能导致程序崩溃、数据损坏或其他不可预测的行为。

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//释放free(p);p = NULL;free(p);//err,多次释放return 0;
}

后果
1.数据损坏:重复释放内存可能导致未定义的行为,包括数据损坏。当重复释放内存后,其他变量可能会被覆盖或改变,导致程序出现逻辑错误或不可预测的行为。
2.安全漏洞:重复释放内存可能导致安全漏洞。恶意攻击者可以利用重复释放内存的漏洞来执行代码注入、缓冲区溢出等攻击。

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

在前面已经多次提及这个问题,在此就不再过多介绍了。


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

在这里插入图片描述


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

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


同时也能解释为什么static修饰局部变量导致其生命周期变长。

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


5 柔性数组

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

例如:

//在C语言中,柔性数组有两种声明形式,具体具体取决与编译器:
//第一种声明方式
struct S
{int i;int arr[];//柔性数组,前面至少有一个成员
};//第二种声明方式
struct S
{int i;int arr[0];//柔性数组
};

5.1 柔性数组的特点

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

例如:

typedef struct S
{int i;int arr[];
}type_s;sizeof("%d\n", sizeof(type_s));//输出的是4

5.2 柔性数组的使用

typedef struct S
{int i;int arr[];
}type_s;int main()
{int i = 0;type_s* p = (type_s*)malloc(sizeof(type_s) + 100 * sizeof(int));if (p == NULL){perror("malloc->type_s");return 1;}//业务处理p->i = 100;for (i = 0; i < 100; i++){p->arr[i] = i + 1;}free(p);p = NULL;return 0;
}

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


5.3 柔性数组的优势

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

typedef struct S
{int i;int* p_a;
}type_s;int main()
{type_s* p = (type_s*)malloc(sizeof(type_s));if (p == NULL){perror("malloc->type_s");return 1;}p->i = 100;int* ptr = realloc(p->p_a, 4 * sizeof(int));if (ptr == NULL){perror("malloc->p_s");return 1;}else{p->p_a = ptr;}//业务处理for (int i = 0; i < 100; i++){p->p_a[i] = i + 1;}//释放free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

上述两种代码可以完成完全相同的功能,但是第一种实现有两个好处:

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

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

第二个好处:有利于访问速度

连续的内存有益于提高访问速度,有利于减少内存碎片。(但其实这个因素产生的结果也高不了多少,反正你跑不了用偏移量的加法来寻址)。

拓展阅读:
C语言结构体的数组和指针


6. 结尾

本篇博客到此就结束了,如果对你有帮助记得三连哦。感谢您的支持!!
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

小程序----配置原生内置编译插件支持sass

修改project.config.json配置文件 在 project.config.json 文件中&#xff0c;修改setting 下的 useCompilerPlugins 字段为 ["sass"]&#xff0c; 即可开启工具内置的 sass 编译插件。 目前支持三个编译插件&#xff1a;typescript、less、sass 修改之后可以将原.w…

持续贡献开源力量,棱镜七彩加入openKylin

近日&#xff0c;棱镜七彩签署 openKylin 社区 CLA&#xff08;Contributor License Agreement 贡献者许可协议&#xff09;&#xff0c;正式加入openKylin 开源社区。 棱镜七彩成立于2016年&#xff0c;是一家专注于开源安全、软件供应链安全的创新型科技企业。自成立以来&…

【消息中间件】原生PHP对接Uni H5、APP、微信小程序实时通讯消息服务

文章目录 视频演示效果前言一、分析二、全局注入MQTT连接1.引入库2.写入全局连接代码 二、PHP环境建立总结 视频演示效果 【uniapp】实现买定离手小游戏 前言 Mqtt不同环境问题太多&#xff0c;新手可以看下 《【MQTT】Esp32数据上传采集&#xff1a;最新mqtt插件&#xff08;支…

使用3ds Max粒子系统创建飞天箭雨特效场景

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 设置箭头 步骤 1 打开 3ds Max。 打开 3ds Max 步骤 2 我使用多边形建模技术制作了一个简单的箭头&#xff0c;我将 在教程中使用。.max您可以从 下载部分。 箭头.max 步骤 3 将此箭头重命名为静态…

【计算复杂性理论】证明复杂性(八):命题鸽巢原理(Propositional Pigeonhole Principle)的指数级归结下界

往期文章&#xff1a; 【计算复杂性理论】证明复杂性&#xff08;Proof Complexity&#xff09;&#xff08;一&#xff09;&#xff1a;简介 【计算复杂性理论】证明复杂性&#xff08;二&#xff09;&#xff1a;归结&#xff08;Resolution&#xff09;与扩展归结&#xff…

CentOS 7.6使用yum安装stress,源码安装stree-ng 0.15.06,源码安装sysstat 12.7.2

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64 yum install stress sysstat -y安装stress和sysstat。 使用pidstat -u 5 1没有%wait项&#xff1a; 原因是CentOS 7仓…

13.7 CentOS 7 环境下大量创建帐号的方法

13.7.1 一些帐号相关的检查工具 pwck pwck 这个指令在检查 /etc/passwd 这个帐号配置文件内的信息&#xff0c;与实际的主文件夹是否存在等信息&#xff0c; 还可以比对 /etc/passwd /etc/shadow 的信息是否一致&#xff0c;另外&#xff0c;如果 /etc/passwd 内的数据字段错…

用C语言构建一个手写数字识别神经网络

(原理和程序基本框架请参见前一篇 "用C语言构建了一个简单的神经网路") &#xff11;&#xff0e;准备训练和测试数据集 从http://yann.lecun.com/exdb/mnist/下载手写数字训练数据集, 包括图像数据train-images-idx3-ubyte.gz 和标签数据 train-labels-idx1-ubyte.…

芯片制造详解.光刻技术与基本流程.学习笔记(四)

本篇文章是看了以下视频后的笔记提炼&#xff0c;欢迎各位观看原视频&#xff0c;这里附上地址 芯片制造详解04&#xff1a;光刻技术与基本流程&#xff5c;国产之路不容易 芯片制造详解.光刻技术与基本流程.学习笔记 四 一、引子二、光刻(1).光掩膜(2).光刻机(3).光刻胶(4).挖…

宝塔设置云服务器mysql端口转发,实现本地电脑访问云mysql

环境&#xff1a;centos系统使用宝塔面板 实现功能&#xff1a;宝塔设置云服务器mysql端口转发&#xff0c;实现本地电脑访问mysql 1.安装mysql、PHP-7.4.33、phpMyAdmin 5.0 软件商店》搜索 mysql安装即可 软件商店》搜索 PHP安装7.4.33即可&#xff08;只需要勾选快速安装&…

按键消抖(有/无状态机)

一&#xff0c;理论概念 按键抖动 按键抖动&#xff1a;按键抖动通常的按键所用开关为机械弹性开关&#xff0c;当机械触点断开、闭合时&#xff0c;由于机械触点的弹性作用&#xff0c;一个按键开关在闭合时不会马上稳定地接通&#xff0c;在断开时也不会一下子断开。因而在闭…

数据结构: 线性表(顺序表实现)

文章目录 1. 线性表的定义2. 线性表的顺序表示:顺序表2.1 概念及结构2.2 接口实现2.2.1 顺序表初始化 (SeqListInit)2.2.2 顺序表尾插 (SeqListPushBack)2.2.3 顺序表打印 (SeqListPrint)2.2.6 顺序表销毁 (SeqListDestroy)2.2.5 顺序表尾删 (SeqListPopBack)2.2.6 顺序表头插 …

安全学习DAY08_算法加密

算法加密 漏洞分析、漏洞勘测、漏洞探针、挖漏洞时要用到的技术知识 存储密码加密-应用对象传输加密编码-发送回显数据传输格式-统一格式代码特性混淆-开发语言 传输数据 – 加密型&编码型 安全测试时&#xff0c;通常会进行数据的修改增加提交测试 数据在传输的时候进行…

【Linux】关于Bad magic number in super-block 当尝试打开/dev/sda1 时找不到有效的文件系统超级块

每个区段与 superblock 的信息都可以使用 dumpe2fs 这个指令来查询的&#xff01; 不过可惜的是&#xff0c;我们的 CentOS 7 现在是以 xfs 为默认文件系统&#xff0c; 所以目前你的系统应该无法使用 dumpe2fs 去查询任何文件系统的。 因为目前两个版本系统的根目录使用的文…

IT职场笔记

MySQL笔记之一致性视图与MVCC实现 一致性读视图是InnoDB在实现MVCC用到的虚拟结构&#xff0c;用于读提交&#xff08;RC&#xff09;和可重复度&#xff08;RR&#xff09;隔离级别的实现。 一致性视图没有物理结构&#xff0c;主要是在事务执行期间用来定义该事物可以看到什…

护网行动:ADSelfService Plus引领企业网络安全新纪元

随着信息技术的飞速发展&#xff0c;企业网络的重要性变得愈发显著。然而&#xff0c;随之而来的网络安全威胁也日益增多&#xff0c;网络黑客和恶意软件不断涌现&#xff0c;给企业的数据和机密信息带来巨大风险。在这个信息安全威胁层出不穷的时代&#xff0c;企业急需一款强…

Ubuntu的安装与部分配置

该教程使用的虚拟机是virtuabox&#xff0c;镜像源的版本是ubuntu20.04.5桌面版 可通过下面的链接在Ubuntu官网下载&#xff1a;Alternative downloads | Ubuntu 也可直接通过下面的链接进入百度网盘下载【有Ubuntu20.04.5与hadoop3.3.2以及jdk1.8.0_162&#xff0c;该篇需要使…

idea 关于高亮显示与选中字符串相同的内容

dea 关于高亮显示与选中字符串相同的内容&#xff0c;本文作为个人备忘的同时也希望可以作为大家的参考。 依次修改File-settings-Editor-Color Scheme-General菜单下的Code-Identifier under caret和Identifier under caret(write)的Backgroud色值&#xff0c;可以参考下图。…

算法leetcode|64. 最小路径和(rust重拳出击)

文章目录 64. 最小路径和&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 64. 最小路径和&#xff1a; 给定一个包含非负整数的 m x n 网…

【linux--->传输层协议】

文章目录 [TOC](文章目录) 一、端口号1.端口号划分范围2.常用知名端口号 二、网络命令1.netstat 命令2.pidof 命令 三、UDP协议1.格式2.协议的分离和合并3.特点4.缓冲区 四、TCP协议1.格式2.4位的数据偏移3.确认应答机制4.序号与确认序号5.16位窗口6.标志位7.超时重传8.三次握手…