当前位置: 首页 > news >正文

《C和指针》笔记36:动态内存分配

1. malloc和free

C函数库提供了两个函数,mallocfree,分别用于执行动态内存分配和释放。这些函数维护一个可用内存池。当一个程序另外需要一些内存时,它就调用malloc函数,malloc从内存池中提取一块合适的内存,并向该程序返回一个指向这块内存的指针。这块内存此时并没有以任何方式进行初始化。如果对这块内存进行初始化非常重要,要么自已动手对它进行初始化,要么使用calloc函数(在下一节描述)。当一块以前分配的内存不再使用时,程序调用free函数把它归还给内存池供以后之需。

这两个函数的原型如下所示,它们都在头文件stdlib.h中声明。

void *malloc( size_t size );
void free( void *pointer );

malloc的参数就是需要分配的内存字节(字符)数 。如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针。

malloc所分配的是一块连续的内存。例如,如果请求它分配100个字节的内存,那么它实际分配的内存就是100个连续的字节,并不会分开位于两块或多块不同的内存。同时,malloc实际分配的内存有可能比你请求的稍微多一点。但是,这个行为是由编译器定义的,所以你不能指望它肯定会分配比你的请求更多的内存。

如果内存池是空的,或者它的可用内存无法满足你的请求,会发生什么情况呢?在这种情况下,malloc函数向操作系统请求,要求得到更多的内存,并在这块新内存上执行分配任务。如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。因此,对每个从malloc返回的指针都进行检查,确保它并非NULL是非常重要的。

free的参数必须要么是NULL,要么是一个先前从malloccallocrealloc(稍后描述)返回的值。向free传递一个NULL参数不会产生任何效果。

malloc又是如何知道所请求的内存需要存储的是整数、浮点值、结构还是数组呢?它并不知情——malloc返回一个类型为void *的指针,正是缘于这个原因。标准表示一个void *类型的指针可以转换为其他任何类型的指针。但是,有些编译器,尤其是那些老式的编译器,可能要求在转换时使用强制类型转换。

对于要求边界对齐的机器,malloc所返回的内存的起始位置将始终能够满足对边界对齐要求最严格的类型的要求。

2. calloc和realloc

另外还有两个内存分配函数,callocrealloc。它们的原型如下所示:

void *calloc( size_t num_elements, size_t element_size );
void realloc( void *ptr, size_t new_size );

calloc也用于分配内存。malloccalloc之间的主要区别是后者在返回指向内存的指针之前把它初始化为0。这个初始化常常能带来方便,但如果程序只是想把一些值存储到数组中,那么这个初始化过程纯属浪费
时间。callocmalloc之间另一个较小的区别是它们请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,它能够计算出总共需要分配的内存。

realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果它用于缩小一个内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。

如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。因此,在使用realloc之后,就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。

最后,如果realloc函数的第1个参数是NULL,那么它的行为就和malloc一模一样。

3. 使用动态分配的内存

这里举一个例子:

int *pi;
pi = malloc(100);
if (pi == NULL)
{printf("Out of memory!\n");exit(1);
}

符号NULL定义于stdio.h,它实际上是字面值常量0。它在这里起着视觉提醒器的作用,提醒我们进行测试的值是一个指针而不是整数。

如果内存分配成功,那么我们就拥有了一个指向100个字节的指针。在整型为4个字节的机器上,这块内存将被当作25个整型元素的数组,因为pi是一个指向整型的指针。

如果目标就是获得足够存储25个整数的内存,这里有一个更好的技巧来实现这个目的。

pi = malloc( 25 * sizeof( int ) );

这个方法更好一些,因为它是可移植的。即使是在整数长度不同的机器上,它也能获得正确的结果。

既然已经有了一个指针,那么该如何使用这块内存呢?当然,可以使用间接访问和指针运算来访问数组的不同整数位置,下面这个循环就是这样做的,它把这个新分配的数组的每个元素都初始化为0:

int *pi2, i;
pi2 = pi;
for (i = 0; i < 25; ++i)
{*pi2++ = 0;
}

不仅可以使用指针,也可以使用下标。下面的第2个循环所执行的任务和前面一个相同。

int *pi2, i;
pi2 = pi;
for (i = 0; i < 25; ++i)
{pi2[i] = 0;
}

4. 常见的动态内存错误

在使用动态内存分配的程序中,常常会出现许多错误。这些错误包括对NULL指针进行解引用操作、对分配的内存进行操作时越过边界、释放并非动态分配的内存、试图释放一块动态分配的内存的一部分以及一块动态内存被释放之后被继续使用。

动态内存分配最常见的错误就是忘记检查所请求的内存是否成功分配。这种问题显而易见:被访问的内存可能保存了其他变量的值。对它进行修改将破坏那个变量,修改那个变量将破坏存储在那里的值。这种类型的bug非常难以发现。下面的程序展现了一种技巧,可以很可靠地进行这个错误检查。MALLOC宏接受元素的数目能及每种元素的类型,计算总共需要的内存字节数,并调用alloc获得内存。alloc调用malloc并进行检查,确保返回的指针不是NULL。

这个方法最后一个难解之处在于第1个非比寻常的#define指令。它用于防止由于其他代码块直接塞入程序而导致的偶尔直接调用malloc的行为。增加这个指令以后,如果程序偶尔调用了malloc,程序将由于语法错误而无法编译。在alloc中必须加入#undef指令,这样它才能调用malloc而不至于出错。

  • alloc.h:错误检查分配器:接口
/*
** 定义一个不易发生错误的内存分配器。
*/
#include < stdlib.h>
#define malloc //不要直接调用malloc!
#define MALLOC(num,type) (type *)alloc( (num) * sizeof(type) )
extern void *alloc( size_t size );
  • alloc.c:错误检查分配器:实现
/*
** 不易发生错误的内存分配器的实现
*/
#include <stdio.h>
#include "alloc.h"
#undef malloc
void *
alloc( size_t size )
{
void *new_mem;
/*
** 请求所需的内存,并检查确实分配成功
*/
new_mem = malloc( size );
if( new_mem == NULL ){
printf( "Out of memory!\n" );
exit( 1 );
}
return new_mem;
}
  • a_client.c:使用错误检查分配器
/*
** 一个使用很少引起错误的内存分配器的程序
*/
#include "alloc.h"
void
function()
{int *new_memory;
/*
** 获得一串整型数的空间
*/
new_memory = MALLOC( 25, int );
/* ... */
}

动态内存分配的第二大错误来源是操作内存时超出了分配内存的边界。例如,如果得到一个25个整型的数组,进行下标引用操作时如果下标值小于0或大于24将引起两种类型的问题。这种问题就不是那么明显。在malloc和free的有些实现中,它们以链表的形式维护可用的内存池。对分配的内存之外的区域进行访问可能破坏这个链表,这有可能产生异常,从而终止程序

当使用free时,可能出现各种不同的错误。传递给free的指针必须是一个从malloccallocrealloc函数返回的指针。传给free函数一个指针,让它释放一块并非动态分配的内存可能导致程序立即终止或在晚些时候终止。试图释放一块动态分配内存的一部分也有可能引起类似的问题,像下面这样:

/*
** 获得10个整型内存空间
*/
pi = malloc( 10 * sizeof(int)) ;
/*
** 释放最后5个整型类型的内存空间,保留前5个内存空间
*/
free(pi +5);
}

释放一块内存的一部分是不允许的。动态分配的内存必须整块一起释放。但是,realloc函数可以缩小一块动态分配的内存,有效地释放它尾部的部分内存。

必须小心在意,不要访问已经被free函数释放了的内存。这个警告看上去很显然,但这里仍然存在一个很微妙的问题。假定对一个指向动态分配的内存的指针进行了复制,而且这个指针的几份拷贝散布于程序各处。无法保证当使用其中一个指针时它所指向的内存是不是已被另一个指针释放。另一方面,必须确保程序中所有使用这块内存的地方在这块内存被释放之前停止对它的使用。

内存泄漏

当动态分配的内存不再需要使用时,它应该被释放,这样它以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏(memory leak)。在那些所有执行程序共享一个通用内存池的操作系统中,内存泄漏将一点点地榨干可用内存,最终使其一无所有。要摆脱这个困境,只有重启系统。

其他操作系统能够记住每个程序当前拥有的内存段,这样当一个程序终止时,所有分配给它但未被释放的内存都归还给内存池。但即使在这类系统中,内存泄漏仍然是一个严重的问题,因为一个持续分配却一点不释放内存的程序最终将耗尽可用的内存。此时,这个有缺陷的程序将无法继续执行下去,它的失败有可能导致当前已经完成的工作统统丢失。

相关文章:

《C和指针》笔记36:动态内存分配

1. malloc和free C函数库提供了两个函数&#xff0c;malloc和free&#xff0c;分别用于执行动态内存分配和释放。这些函数维护一个可用内存池。当一个程序另外需要一些内存时&#xff0c;它就调用malloc函数&#xff0c;malloc从内存池中提取一块合适的内存&#xff0c;并向该…...

C/S架构学习之基于UDP的本地通信(服务器)

基于UDP的本地通信&#xff08;服务器&#xff09;&#xff1a;创建流程&#xff1a;一、创建数据报式套接字&#xff08;socket函数&#xff09;&#xff1a; int sock_fd socket(AF_UNIX,SOCK_DGRAM,0);if(-1 sock_fd){perror("socket error");exit(-1);}二、创建…...

excel如何加密(excel加密的三种方法)

Excel是一款广泛使用的办公软件&#xff0c;有时候我们需要对一些重要的Excel文件进行加密&#xff0c;以保证文件的安全性。下面将介绍3种常用的Excel加密方法。 方法一&#xff1a;通过路径文件-另存为-工具-常规选项-设置打开或修改权限密码&#xff08;密码只可以使数字、字…...

玩了个锤子游戏小程序搭建流程:探索深度与逻辑的结合

随着移动互联网的普及&#xff0c;小程序已经成为了越来越多用户的选择。在这个背景下&#xff0c;玩了个锤子游戏小程序应运而生&#xff0c;它为用户提供了一个全新的游戏体验。那么&#xff0c;如何搭建这样一个小程序呢&#xff1f;本文将为大家详细介绍玩了个锤子游戏小程…...

召回率计算及影响因素

召回率是指在所有正样本中&#xff0c;被成功预测为正样本的样本数占比。在机器学习领域&#xff0c;召回率是评估模型预测性能的重要指标之一。在本文中&#xff0c;我们将从多个方面深入探讨召回率的概念和应用。 阈值越高&#xff0c;精准率越高&#xff0c;召回率越低&…...

在Qt中怎么由函数定义自动创建函数实现模板

2023年11月12日&#xff0c;周日凌晨...

【算法】算法题-20231112

算法题 一、459. 重复的子字符串二、414. 第三大的数三、520. 检测大写字母四、680. 验证回文串 II五、283. 移动零 一、459. 重复的子字符串 简单 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true…...

目标检测YOLO实战应用案例100讲-基于多目标追踪的交通场景异常检测(续)

目录 3.5特征融合网络改进:小目标检测层 3.5.1小目标检测层 3.6实验结果与讨论...

最新支付宝转卡码生成之转账源代码(隐藏部分卡号)

一、需要准备好自己的卡号、名称、以及对应的姓名 二、然后将自己的信息填入下面的代码中 三、然后将拼接好的代码&#xff0c;利用转码技术生产对应的二维码 四、这样一个跳转银行卡二维码的转账码就做好了 效果演示&#xff1a;如下 支付宝扫码、跳转码、转卡码、隐藏卡号…...

聊天机器人框架Rasa资源整理

Rasa是一个主流的构建对话机器人的开源框架&#xff0c;它的优点是几乎覆盖了对话系统的所有功能&#xff0c;并且每个模块都有很好的可扩展性。参考文献收集了一些Rasa相关的开源项目和优质文章。 一.Rasa介绍 1.Rasa本地安装 直接Rasa本地安装一个不好的地方就是容易把本地…...

魔搭社区LLM模型部署实践, 以ChatGLM3为例(一)

魔搭社区LLM模型部署实践&#xff0c; 以ChatGLM3为 例 本文以ChatGLM3-6B为例&#xff0c; 主要介绍在魔搭社区如何部署LLM&#xff0c; 主要包括如下内容&#xff1a; ● SwingDeploy - 云端部署&#xff0c; 实现零代码一键部署 ● 多端部署 - MAC个人笔记本&#xff0c;…...

25期代码随想录算法训练营第十四天 | 二叉树 | 层序遍历(10道题)、226.翻转二叉树 、101.对称二叉树 2

目录 层序遍历 10226.翻转二叉树101.对称二叉树 2 层序遍历 10 链接 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right clas…...

ubuntu cp210x(USB转串口)驱动安装教程

ubuntu cp210x&#xff08;USB转串口&#xff09;驱动安装 前言1. 驱动下载2. 驱动编译和安装3. 可能遇到的问题及解决办法3.1 第一种解决方案&#xff1a;检查当前是否已有CP210x.ko驱动3.2 第二种解决方案&#xff1a;修改驱动文件&#xff0c;编译 前言 CP2108 是一款高度集…...

Spring-SpringAOP的实现

对Spring AOP的理解 OOP表示面向对象编程&#xff0c;是一种编程思想&#xff0c;AOP表示面向切面编程&#xff0c;也是一种编程思想 Spring AOP&#xff1a;Spring为了让程序员更加方便的做到面向切面编程所提供的技术支持 Spring提供的一套机制&#xff0c;让我们更容易的…...

js:Browserslist用特定语句查询浏览器列表的工具与Babel和Postcss配置使用

目录 BrowserslistBabel 和 BrowserslistPostcss 和 Browserslist推荐配置参考文章 Browserslist Browserslist 是一个用特定语句查询浏览器列表的工具 文档 https://www.npmjs.com/package/browserslisthttps://github.com/browserslist/browserslist#full-list 安装 pnp…...

odoo16前端框架源码阅读——boot.js

odoo16前端框架源码阅读——boot.js 从名字就能看出来&#xff0c;这个文件是一个启动文件。 odoo前端将所有的js打包成了两个文件&#xff0c;一个是common.js,另一个是backend.js, 而common.js 是最先加载的 在common.js打包的js文件中最先加载的是下面的文件&#xff0c;看…...

使用MybatisPlus时出现的java.lang.NullPointerException异常~

错误描述如下所示&#xff1a; 错误原因&#xff1a;Junit的导包错误 单元测试的包有如下所示两个 我们应该根据springboot的版本进行选择&#xff0c; 在Spring Boot 2.2.X以后使用import org.junit.jupiter.api.Test Junit5 在Spring Boot 2.2.x之前使用import org.junit.T…...

27 微服务配置拉取

1&#xff09;引入nacos-config依赖 首先&#xff0c;在user-service服务中&#xff0c;引入nacos-config的客户端依赖&#xff1a; <!--nacos配置管理依赖--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-sta…...

hutool ExcelUtil导出excel二级表头

简介&#xff1a;Hutool是一款十分好用的开发工具集&#xff0c;里面包含了大部分日常开发常用的工具&#xff0c;使用简单方便&#xff0c;可以大大提升日常开发效率&#xff0c;十分推荐大家使用。这里简单总结一下基于Hutool的Excel使用。 一、Hutool依赖 <!-- Excel导出…...

《开箱元宇宙》:认识香港麦当劳通过 The Sandbox McNuggets Land 的 Web3 成功经验

McNuggets Land 是 The Sandbox 于 2023 年发布的最受欢迎的体验之一。在本期的《开箱元宇宙》系列中&#xff0c;我们采访了香港麦当劳数位顾客体验暨合作伙伴资深总监 Kai Tsang&#xff0c;来了解这一成功案例背后的策略。 在不断发展的市场营销和品牌推广领域&#xff0c;不…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

DAY 26 函数专题1

函数定义与参数知识点回顾&#xff1a;1. 函数的定义2. 变量作用域&#xff1a;局部变量和全局变量3. 函数的参数类型&#xff1a;位置参数、默认参数、不定参数4. 传递参数的手段&#xff1a;关键词参数5 题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一…...