【C语言进阶(11)】动态内存管理

文章目录

  • Ⅰ 存在动态内存分配的原因
  • Ⅱ 动态内存函数
    • 1. malloc
    • 2. calloc
    • 3. realloc
    • 4. free (重要)
  • Ⅲ 常见动态内存错误
    • 1. 对 NULL 指针的解引用操作
    • 2. 对动态开辟空间的越界访问
    • 3. 对非动态开辟内存使用 free 释放
    • 4. 使用 free 释放一块动态开辟内存的一部分
    • 5. 对同一块动态内存多次释放
    • 6. 动态开辟内存没有释放(内存泄漏)
  • Ⅳ 柔性数组

引用头文件

stdlib.h

Ⅰ 存在动态内存分配的原因

内存使用的方式

  1. 创建变量(开辟一块独立的空间)

    • 局部变量 -> 栈区。
    • 全局变量 -> 静态区。
  2. 创建数组(开辟一块连续的 空间)

    • 局部数组 -> 栈区。
    • 全局数组 -> 静态区。

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

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

存在的问题

  • 不能灵活的开辟空间。如果需要的空间很少,但是固定开辟的空间非常大,就会造成内存浪费。如果需要的空间很多,固定开辟的空间很小,就会造成内存不够用的情况。
  • 因此就出现了动态内存函数,用来随时更改所开辟的空间的大小。

Ⅱ 动态内存函数

1. malloc

函数原型

void* malloc (size_t size);

函数功能

  • 开辟一块 size 个字节大小连续可用的空间,并返回该空间的起始地址。

函数返回值

  • 如果开辟成功,则返回一个指向开辟好空间的起始地址的指针。
  • 如果开辟失败,则返回一个 NULL 指针。因此要检查好 malloc 的返回值。
  • 返回值的类型是 void*,在使用指向开辟好的空间的起始地址的指针时,还需要转换成自己需要的指针类型。

函数用例

  • 申请一块空间,用来存放 10 个整形。
#include <stdlib.h>int main()
{int* p = (int*)malloc(10 * sizeof(int));//malloc 返回的是 void* 的指针,需要转换成对应的指针类型才能使用return 0;
}

2. calloc

函数原型

void* calloc (size_t num, size_t size);

函数功能

  • 为 num 个大小为 size 的元素开辟一块空间,并且把该空间的每个字节初始化为 0。
  • 与 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

函数用例

  • 开辟一块具有 10 个元素,每个元素 4 个字节的空间。

在这里插入图片描述

3. realloc

函数原型

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

函数功能

  • 调整由 ptr 所指向的空间大小为 size 个字节。

函数参数

  • ptr:指向由 malloc、calloc 所开辟的空间。
  • size:新空间的大小(字节)。

realloc 函数的两种使用情况

void* realloc (void* ptr, size_t size);
  1. 将 ptr 所指向的空间大小重新调整为 size 个字节。
void* realloc(NULL,size_t size);
  1. 开辟一块有 size 个字节的空间。

函数用例

在这里插入图片描述

realloc 调整空间的方式

realloc在调整内存空间时存在两种情况

  1. 情况 1:原有空间之后足够大的空间,扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧地址

在这里插入图片描述

  1. 情况 2:原有空间之后没有足够大的空间,realloc 会重新找一个内存区域开辟一块符合要求的空间,返回新地址,拷贝旧数据,释放旧空间

在这里插入图片描述

4. free (重要)

函数原型

void free (void* ptr);

函数参数

  • ptr:指向先前用 malloc、calloc、realloc 分配的内存块的起始地址。

函数功能

  • 释放由 ptr 指向的动态开辟的空间。

函数用例

  • 释放由各个动态内存函数所开辟的空间。
int main()
{int* p1 = (int*)malloc(10 * sizeof(int));int* p2 = (int*)calloc(10, sizeof(int));int* p3 = (int*)realloc(NULL, sizeof(int) * 10);//......free(p1);free(p2);free(p3);return 0;
}

注意事项

  • free 会将指针指向的空间释放掉,但是不会改变指针内存放的地址值。
  • 也就是说,即使 free 掉了 p1 p2 p3 三个指针所指向的空间,这三个指针依然记得原先空间的起始地址。
  • 因此释放掉指针所指向的空间之后,还必须要将该指针变量的值置为 NULL。
int main()
{int* ptr = (int*)malloc(10 * sizeof(int));//......free(ptr);	//释放掉ptr指向的空间后,ptr内存的地址值未变,还是记得原来空间的地址ptr = NULL;	//因此要主动修改ptr内存放的地址值为NULLreturn 0;
}

Ⅲ 常见动态内存错误

1. 对 NULL 指针的解引用操作

  • 在使用之前未判断动态内存函数的返回值是否是 NULL,如果内存开辟失败返回的是 NULL,就成了对 NULL 指针的解引用操作。
int* p = (int*)malloc(40);
*p = 20;//使用前为判断指针 p 内的值是否有效

正确做法

int* p = (int*)malloc(40);if (NULL == p)	//使用之前先判断该空间是否开辟成功
{return 1;	//1 为异常返回
}*p = 20;

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

  • 动态申请的空间也有着自己的范围,不能无限制的使用。
int* p = calloc(10, sizeof(int));if (NULL == p)
{perror("calloc");return 1;
}for (int i = 0; i <= 10; i++)//当 i 到 10 时越界访问
{printf("%d ", *(p + i));
}
free(p);
p = NULL;

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

  • free 只能释放由 malloc、calloc、realloc 开辟的空间。

在这里插入图片描述

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

  • 指向开辟空间起始地址的指针产生变动,没有从动态开辟内存的起始地址开始释放空间。

在这里插入图片描述

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

  • 忘记了已经释放过动态开辟的空间,又重新释放了一遍。
int* p = (int*)malloc(10 * sizeof(int));
//......
free(p);//此时已经释放过该空间一次
//.....
free(p);//睡蒙了又释放一次

解决办法

  • 在释放完动态开辟好的内存后,及时将该指针置为 NULL。之后如果再重复释放也不会产生问题。

6. 动态开辟内存没有释放(内存泄漏)

  • 只记得申请空间,不记得还。
void test()
{int* p = (int*)malloc(10 * sizeof(int));//p 是局部变量,出了作用域就销毁,等这个函数一结束就没人再记得这块空间的起始地址了if (true)//此时动态开辟的空间永远没机会释放了{return;}free(p);p = NULL;
}int main()
{test();return 0;
}

Ⅳ 柔性数组

  • C99 中,结构中的最后一个元素允许是未知大小数组,这就叫做『柔性数组』成员。
struct S
{char c;int i;int arr[];	//未知大小数组 - 柔性数组成员
};

柔性数组的特点

  • 结构体中的柔性数组成员前面必须至少有一个其他成员。
  • sizeof 返回的这种结构体大小不包含柔性数组成员的大小。
  • 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
struct S
{char c;int i;int arr[];
};int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);//这 20 个字节才是分配给柔性数组 arr 的return 0;
}

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

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

相关文章

《深度不确定条件下的决策:从理论到实践》PDF

制定未来计划时需要预测变化&#xff0c;尤其是制定长期计划或针对罕见事件的计划时。当这些变化存在高度不确定性的时候&#xff0c;这种预期就变得越来越困难。 今天给大家介绍的这本《深度不确定条件下的决策&#xff1a;从理论到实践》正是解决以上问题的良方。完整书籍文…

数据结构与算法-顺序表

数据结构与算法 &#x1f388;1.线性表&#x1f50e;1.1基本操作&#x1f50e;1.2线性表的存储结构 &#x1f388;2.线性表的顺序表示和实现&#x1f50e;2.1线性表的顺序存储表示&#x1f52d;2.1.1静态顺序表&#x1f52d;2.1.2动态顺序表 &#x1f50e;2.2顺序表基本操作的实…

安卓:解决AndroidStudio导出Unity的Apk(APP)出现2个显示图标

用AndroidStudio打开该项目 实现只保留1个app图标 AndroidManifest.xml的改法如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android" package"com.fru…

【开发篇】十四、SpringBoot整合Quartz实现定时任务

文章目录 1、关于定时任务2、Java原生实现3、相关名词4、SpringBoot整合Quartz5、Quartz的通用配置6、关于QuartzJobBean7、关于调度器Scheduler的绑定8、Quartz持久化 1、关于定时任务 定时任务在实际开发中使用场景很多&#xff0c;比如&#xff1a; 年度报告各种统计报告某…

【小程序 - 加强】自定义组件、使用npm包、全局数据共享、分包_05

目录 一、自定义组件 1. 组件的创建与引用 1.1 创建组件 1.2 引用组件 1.2.1 局部引用组件 1.2.2 全局引用组件 1.2.3 全局引用 VS 局部引用 1.2.4 组件和页面的区别 2. 样式 2.1 组件样式隔离 2.2 组件样式隔离的注意点 2.3 修改组件的样式隔离选项 2.4 styleIso…

C++(List)

本节目标&#xff1a; 1.list介绍及使用 2.list深度剖析及模拟实现 3.list和vector对比 1.list介绍及使用 1.1list介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;…

内网综合扫描工具-fscan的安装和使用

简介 一款内网综合扫描工具&#xff0c;方便一键自动化、全方位漏扫扫描。 支持主机存活探测、端口扫描、常见服务的爆破、ms17010、redis批量写公钥、计划任务反弹shell、读取win网卡信息、web指纹识别、web漏洞扫描、netbios探测、域控识别等功能。相当强大&#xff01;&…

国庆10.04

服务器 代码 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> //服务器头文件 #include<QTcpSocket> //客户端头文件 #include<QList> //链表容器 #include<QMessag…

OpenCV(Python)的二值化示例

# -*-coding:utf-8-*- # src&#xff1a; 输入图&#xff0c;只能输入单通道图像&#xff0c;通常来说为灰度图 # - dst&#xff1a; 输出图 # - thresh&#xff1a; 阈值 # - maxval&#xff1a; 当像素值超过了阈值&#xff08;或者小于阈值&#xff0c;根据type来决定&#…

【重拾C语言】二、顺序程序设计(基本符号、数据、语句、表达式、顺序控制结构、数据类型、输入/输出操作)

目录 前言 二、顺序程序设计 2.1 求绿化带面积——简单程序 2.2基本符号&#xff1a; 2.2.1 字符集 可视字符 不可视字符 2.2.2 C特定符 关键字 分隔符 运算符 2.2.3 标识符 2.2.4 间隔符 2.2.5 注释 2.3 数据 2.3.1 字面常量&#xff08;Literal Constants&am…

扫雷小游戏(简单详细)(内附完整代码)

设计总体思路 实现游戏可以一直玩&#xff0c;先打印棋盘&#xff0c;玩家和电脑下棋&#xff0c;最后分出胜负。 如果编写较大的程序&#xff0c;我们可以分不同模块 例如这个扫雷&#xff0c;我们可以创建三个文件 分别为&#xff1a; game.h 函数的声明game.c 函数的…

MySQL数据库基础回顾与复习

MySQL数据库 一、原理定义概念 定义 数据库(Database)是按照数据结构来组织、存储和管理数据的建立在计算机存储设备上的仓库 数据库是长期储存在计算机内、有组织的、可共享的数据集合 分类&#xff1a; &#xff08;1&#xff09;非结构化数据&#xff1a; 数据相对来讲没…

微软输入法如何打勾和箭头的符号

文章目录 一、打 “√” 符号二、打 “←” 和 “→” 符号 一、打 “√” 符号 选中 “表情包” 图标 选中 “Ω” 符号后&#xff0c;下拉找到 “√” 即可。 微软输入法打 “ ”这个符号直接输入拼音“cha”就行。 二、打 “←” 和 “→” 符号 拼音直接打 “zuo” 或 “…

苹果ios系统IPA包企业签名手机下载应用可以有几种方式可以下载到手机?

一、App Store签名&#xff1a;这是最常见和推荐的苹果签名方式。用户可以通过苹果的官方应用商店App Store下载并安装经过苹果审核的应用程序。这种签名方式确保了应用程序的安全性和可靠性&#xff0c;因为App Store对应用进行了严格的审核和验证。 二、企业签名&#xff1a;…

【Kafka专题】Kafka集群架构设计原理详解

目录 前言前置知识课程内容一、Kafka的Zookeeper元数据梳理1.1 zookeeper整体数据1.2 Controller Broker选举机制1.3 Leader Partition选举机制1.4 Leader Partition自动平衡机制*1.5 Partition故障恢复机制1.6 HW一致性保障-Epoch更新机制1.7 总结 学习总结感谢 前言 Kafka的…

【VUE复习·9】v-for 基础用法(循环渲染也叫列表渲染)

总览 1.v-for 都能循环什么 2.用法 一、v-for 都能遍历什么 能循环的东西包括&#xff1a;数组、对象、字符串&#xff08;和java里面的3个引用数据类型一样&#xff09;、纯粹循环数量&#xff08;少用&#xff09; 二、用法 1.用法1&#xff1a;简单循环&#xff08;遍历…

扩散模型diffusion model 代码解读

代码来自这里 使用pytorch轻松实现简单扩散模型diffusion model&#xff08;附可跑通全部代码&#xff09; - 知乎 1.作者首先自己定义了一个数据集&#xff0c;也就是一堆散点&#xff0c;组成的S。 2.这些都是预先设置好的参数&#xff0c;也就是利用这些来做learning的提示…

【OLSR路由协议】链路状态路由(OLSR)协议中选择多点中继节点算法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Flink--7、窗口(窗口的概念、分类、API、分配器、窗口函数)、触发器、移除器

星光下的赶路人star的个人主页 内心的平静始于不再让他人掌控你的感情 文章目录 0、前言1、窗口&#xff08;Window&#xff09;1.1 窗口的概念1.2 窗口的分类1.3 窗口API概览1.4 窗口分配器&#xff08;Window Assigner&#xff09;1.4.1 时间窗口1.4.2 计数窗口 1.5 窗口函数…

【Linux基础】Linux云服务器(腾讯云、阿里云、华为云)环境部署 | 安装远程XShell | 基本账号管理(超详细教程)

&#x1f449; 系列专栏&#xff1a;【LLinux基础】 &#x1f648; 个人主页&#xff1a;sunnyll 目录 一、前言 二、 Linux环境安装 &#x1f4a6; Linux 环境的搭建方式 &#x1f4a6;如何购买云服务器 三、 安装远程控制XShell &#x1f4a6;下载 XShell &#x1f4…