《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)。在那些所有执行程序共享一个通用内存池的操作系统中,内存泄漏将一点点地榨干可用内存,最终使其一无所有。要摆脱这个困境,只有重启系统。

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

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

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

相关文章

Linux imu6ull驱动- led

一、GPIO模块结构 开始来啃手册了&#xff0c;打开我们的imx6ull手册。本章我们编写的是GPIO的&#xff0c;打开手册的第28章&#xff0c;这一章就有关于IMX6ULL 的 GPIO 模块结构。 mx6ull一共有5 组 GPIO&#xff08;GPIO1&#xff5e;GPIO5&#xff09; GPIO1 有 32 个引脚&…

C/C++输出硬币翻转 2021年6月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C硬币翻转 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C硬币翻转 2021年6月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 假设有N个硬币(N为不大于5000的正整数)&#xff0c;从1…

C语言--汉诺塔【内容超级详细】

今天与大家分享一下如何用C语言解决汉诺塔问题。 目录 一.前言 二.找规律⭐ 三.总结⭐⭐⭐ 四.代码实现⭐⭐ 一.前言 有一部很好看的电影《猩球崛起》⭐&#xff0c;说呀&#xff0c;人类为了抗击癌症发明了一种药物&#x1f357;&#xff0c;然后给猩猩做了实验&#xff0…

2020年五一杯数学建模A题煤炭价格预测问题解题全过程文档及程序

2020年五一杯数学建模 A题 煤炭价格预测问题 原题再现 煤炭属于大宗商品&#xff0c;煤炭价格既受国家相关部门的监管&#xff0c;又受国内煤炭市场的影响。除此之外&#xff0c;气候变化、出行方式、能源消耗方式、国际煤炭市场等其他因素也会影响煤炭价格。请完成如下问题。…

canvas 简单直线轨迹运动与线性插值计算

canvas 简单直线轨迹运动与线性插值计算 一、canvas 直线轨迹运行 添加 canvas 语法提示 通过/** type {HTMLCanvasElement} */代码块 来添加canvas的语法提示 <body><canvas id"canvas"></canvas> </body> <script>/** type {HTM…

软件模拟SPI协议的理解和使用编写W25Q64

SPI软件模拟的时序 SPI协议中&#xff0c;NSS、SCK、MOSI由主机产生&#xff0c;MISO由从机产生&#xff0c;在SCK每个时钟周期MOSI、MISO传输一位数据&#xff0c;数据的输入输出是同时进行的&#xff0c;所以读写数据也可以视作交换数据。所以读写时对数据位的控制都是用同一…

ke10javaweb停更

<jsp:getProperty property"isval" name "username"/> property来修改属性,name是类 所以如果调用tip在前面那么就是没有设置值 证明是先到java里面去运转

AI编程工具:一站式编程解决方案,引领AI编程新时代

在人工智能的巨浪之下&#xff0c;编程领域正在经历一场深刻的转型。这场转型的核心&#xff0c;就是AI智能编程工具的出现。它们为开发者提供了一种全新的编程方式&#xff0c;极大地提高了编程效率。在这场变革中&#xff0c;DevChat无疑是引领者之一。 一、DevChat&#xf…

计算机毕业设计 基于SpringBoot高校毕业与学位资格审核系统的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

AMD发布大小核 CPU,6核心直接砍成单核了

2022年 Intel 第12代酷睿发布&#xff0c;PE 大小核设计被正式带到了 PC 上。 P-Core 也就是传统的大核有着高性能、高功耗&#xff0c;而 E-Core 小核则是更讲究能效比以更低频率运行。 虽说小蝾也曾有对 Windows 调度方面的怀疑&#xff0c;但多线程性能确实实打实证明了其优…

2023年最受欢迎的9款产品原型工具大揭秘!

1.Pop (Prototyping on Paper) 价格&#xff1a;免费试用30天专业版960RMB/人/年 学习曲线&#xff1a;低 简介&#xff1a;POP是设计界面的原型工具&#xff0c;适用于iOS和Android平台。在POP的帮助下&#xff0c;开发人员或设计师只需在纸上简单地描述创意或想法&#xff…

SPI简介及FPGA通用MOSI模块实现

简介 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外围设备接口&#xff09;通讯协议&#xff0c;是Motorola公司提出的一种同步串行接口技术。是一种高速、全双工、同步通信总线。在芯片中只占用四根管脚用来控制及数据传输。 优缺点&#xff1a; SPI通讯协…

向量数据库:释放数据潜能,重塑信息世界

前言 想必各位开发者一定使用过关系型数据库MySQL去存储我们的项目的数据&#xff0c;也有部分人使用过非关系型数据库Redis去存储我们的一些热点数据作为缓存&#xff0c;提高我们系统的响应速度&#xff0c;减小我们MySQL的压力。那么你有听说过向量数据库吗&#xff1f;知道…

白嫖阿里云服务器教程来了,薅秃阿里云!

白嫖阿里云服务器攻略来了&#xff0c;在阿里云免费试用中心可以申请免费服务器&#xff0c;但是阿里云百科不建议选择免费的&#xff0c;只有3个月使用时长&#xff0c;选择99元服务器不是更香&#xff0c;2核2G配置3M固定带宽&#xff0c;一年99元&#xff0c;重点是新老用户…

【Linux】-文件操作(重定向、缓冲区以及Linux下一切皆文件的详解)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

如何自己实现一个丝滑的流程图绘制工具(九) 自定义连接线

背景 产品又有更近的想法了&#xff0c;bpmn-js的连接线你用的时候是看不到的&#xff0c;也就是你从左侧点击连接线的没有线随鼠标移动. 但是产品想要看得见的连接线移动拖拽。 咩有办法&#xff0c;不能换框架&#xff0c;那就只能自己实现啦&#xff01; 思路&#xff1a; …

06-MySQL-进阶-视图存储函数存储过程触发器

涉及资料 链接&#xff1a;https://pan.baidu.com/s/1M1oXN_pH3RGADx90ZFbfLQ?pwdCoke 提取码&#xff1a;Coke 一、视图 数据准备 create table student(id int auto_increment comment 主键ID primary key,name varchar(10) null comment 姓名,no varchar(10) null co…

JDBC(一)

第1章&#xff1a;JDBC概述 1.1 数据的持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上**&#xff0c;而持久化的实现过程大多通过各种…

利用中断做数码表

功能要求:1.按下KEY1&#xff0c;显示数字开始每0.5秒加1&#xff0c;加到&#xff08;10学号&#xff09;返回0&#xff0c;0显示2秒后继续开始重复加1。 2. 任何时候按下KEY2数字清零&#xff0c;并停止加1。 3. KEY1和KEY2分别采用查询和外部中断方式。 要求程序中有硬件…

概念解析 | 高光谱图像:揭开自然世界的神秘面纱

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:高光谱图像 高光谱图像:揭开自然世界的神秘面纱 Hyperspectral imaging - Wikipedia 背景介绍 我们生活的世界充满了丰富多彩的颜色。这些颜色来源于各种物体反射或吸收不同波长…
最新文章