C/C++动态分配内存的几种方法

2023-10-31

  使用C/C++编程时,会经常动态分配内存,以便合理使用内存,本文主要讲述动态内存分配的几种方法及一些原理,理解不深刻之处欢迎指教。

引言

  为什么要进行动态内存分配?以数组为例,数组元素在内存中存储的地址是连续的。声明一个数组后,该数组需要的内存大小在程序编译时就被分配,但是该数组实际所需内存大小在程序运行时才知道,若运行时发现数组所需内存大于编译时的分配好的数组内存,就会报错,示例程序如下:

#include <stdio.h>
int main(void)
{
	int arr[3];
	for (int i = 0; i < 5; i++)
		scanf("%d", &arr[i]);
	for (int i = 0; i < 5; i++)
		printf("%d ", arr[i]);
	printf("\n");
	return 0;
}

  首先声明了有三个元素的数组arr,但是在实际输入时输入了5个元素,也就是说编译器在编译时给arr分配了3个int型大小的内存块,但是在运行时需要5个int型大小的内存块,就会报如下错误。
在这里插入图片描述

  因此人们采取了一个简单的方法,就是声明一个足够大的数组,例如int arr[1000]。但是该方法有以下缺点:
(1)若程序所需的数组元素个数大于1000,也会造成上述错误;
(2)若程序所需的数组元素个数只有5,会浪费很大的内存空间;
(3)若程序所需的数据元素个数大于1000,为了提高程序的鲁棒性,应该做出合理的响应,而不是简单报错。
  为了解决上述问题,就需要动态分配内存,所谓动态分配内存,通俗理解是需要多大的内存,就分配多大的内存。在C语言中,与动态内存分配有关的函数为malloc、calloc、realloc、free,这些函数在库文件<stdlib.h>中声明;C++中,与动态内存分配有关的运算符为new、delete。

malloc

  malloc是C语言提供的执行内存分配的函数。malloc函数原型为"void *malloc(size_t size);",当程序调用malloc函数时,malloc就从内存池中提取相应大小的连续内存块,并返回指向该内存块起始位置的指针。示例程序如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int *p;
	int count;
	scanf("%d", &count);          //输入元素个数
	p = (int *)malloc(count*sizeof(int));//分配内存空间,将首地址赋给p
	if (p == NULL)                   //若分配失败,报错
		exit(-2);

	for (int i = 0; i < count; i++)
		scanf("%d", p + i);
	for (int i = 0; i < count; i++)
		printf("%d ", *(p + i));
	printf("\n");
	free(p);
	return 0;
}

  关于malloc函数,注意以下几点:
(1)malloc申请的内存是连续的,由于数组的内存也是连续的,因此二者有相通之处,*(p+i)可以表示为数组的第(i+1)个元素;
(2)如果内存池是空的,malloc会向操作系统请求得到更多的内存,然后进行分配;如果操作系统无法向malloc提供更多的内存,malloc就会返回NULL指针,NULL其实就是一个宏定义,值为0,因此对p的值检查是十分必要的,如果p为NULL,表示没分配到内存,自然就不能进行接下来的步骤了。
(3)观察函数原型"void *malloc(size_t size);"可知,malloc返回的是void *类型的指针,这是因为malloc无法得知程序用分配好的内存块存储什么类型的数据(例如int、char、double等),因此返回void *类型,而void *类型可以转为其他任何类型的指针,上述程序就将void *类型强制转换为int *类型,以便该内存块存储int型的数据。
(4)从指针角度看,因为指针存储的是变量的地址,是一个地址值,因此“p = (int )malloc(countsizeof(int))”这个操作可以理解为对指针p初始化。

calloc

  calloc也用于内存分配,函数原型为"void *calloc(size_t num_elements,size_t element_size);",示例程序如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int *p;
	int count;
	scanf("%d", &count);
	p = (int *)calloc(count,sizeof(int));
	if (p == NULL)
		exit(-2);
	for (int i = 0; i < count; i++)
		printf("%d ", *(p + i));
	printf("\n");
	free(p);
	return 0;
}

  程序运行结果如下:
在这里插入图片描述
  该函数和malloc有以下区别:
(1)申请完内存块后,calloc将该内存块存储的值初始化为0,然后才返回指向该内存块起始位置的指针;
(2)两个函数请求内存大小的方式不同,可见参数列表。

realloc

  realloc主要用于修改已经分配的内存的大小,函数原型为“void *realloc(void *ptr,size_t new_size);”,ptr代表已经分配内存的首地址,new_size代表修改后的大小。若用于扩大一个内存块,该内存块原先的内容保留,新增加的内存添加到原先的内存块的后面,并且新增加的部分没有初始化;若用于缩小一个内存块,该内存块的尾部的部分内存被裁减掉,前面的部分内存的内容依然保留。示例程序如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int *p,*newbase;
	int count;
	scanf("%d", &count);
	p = (int *)malloc(count*sizeof(int));
	if (p == NULL)
		exit(-2);
	for (int i = 0; i < count; i++)
		scanf("%d", p + i);
	for (int i = 0; i < count; i++)
		printf("%d ", *(p + i));
	printf("\n");
    
	newbase = (int *)realloc(p, 2*count*sizeof(int));
	if (newbase == NULL)
		exit(-2);
	for (int i = count; i < 2 * count; i++)
		scanf("%d", newbase + i);
	for (int i = 0; i < 2*count; i++)
		printf("%d ", *(newbase + i));
	printf("\n");
	free(newbase);
	return 0;
}

  程序运行结果如下:
在这里插入图片描述
  关于该函数,注意:若原先的内存块的大小无法改变,realloc将分配另一块指定大小的内存块,并将原先内存块的内容复制到新的内存块上。若发生这种情况,内存块的首地址就发生了改变,因此应使用新的指针接收realloc函数的返回值。

free

  当动态分配的内存不再需要时,应该将其释放掉,以便以后重新分配,若使用完不释放会引起内存泄漏。free函数用来释放malloc、calloc、realloc申请的内存空间,函数原型为“void free(void *ptr)”,ptr是指向要释放的内存块的指针,该函数没有返回值,free的参数若是NULL,则free不会产生任何效果。示例程序如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int *p;
	p = (int *)calloc(5, sizeof(int));
	if (p == NULL)
		exit(-2);
	printf("地址和值分别为:%p %d\n", p, *p);
	for (int i = 0; i < 5; i++)
		printf("%d ", *(p + i));
	printf("\n");
	free(p);
	printf("释放后地址和值分别为:%p %d\n", p, *p);
	p = NULL;
	printf("地址为:%p\n", p);
	return 0;
}

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

  注意:使用free释放的是内存块,而不是ptr这个指针变量,ptr的这个指针变量仍然指向该内存空间,只不过该内存空间的内容是毫无意义的是,是未定义的,为了防止以后程序不小心使用了它,要把它指向NULL。

new

new是C++的一个关键字,也是操作符,C语言并没有new,new也经常被用于动态内存分配,示例程序如下:

#include <iostream>
using namespace std;
int main(void)
{
	int *p, *q,*s;
	p = new int; //申请分配一个存储int型数据的内存块,相当于"p=(int *)malloc(sizeof(int));"
	q = new int(0); //申请分配一个存储int型数据的内存块,并初始化为0,相当于"q=(int *)calloc(sizeof(int))";
	s = new int[10]; //申请分配十个存储int类型数据的内存块,相当于"s=(int *)malloc(10*sizeof(int))";
	delete p;
	delete q;
	delete[] s;
	return 0;
}

  它和malloc有如下区别:
(1)new申请内存分配时无需指定内存块的大小,编译器会自己计算,malloc则需要使用sizeof等函数计算好内存块的大小;
(2)若申请成功,new会返回指定类型的指针,不需再进行类型转换;而malloc返回的是void *类型的,还需强制转换成我们需要的类型;
(3)若申请失败,new会抛出bac_alloc异常,而malloc会返回NULL。
  关于更详细的解释,可见博客动态内存分配、malloc与new的区别,这篇博客从更深层次的角度分析了malloc和new的区别。

delete

  new申请的内存块不被释放,也会造成内存泄露,C++主要使用delete来释放new申请的内存。delete用于释放new申请的单个元素分配的内存,delete[]用于释放new []申请的多个元素分配的内存,具体使用方法见上述程序。注意:使用delete释放的是内存块,而不是s这个指针变量,释放完后,s就变为随机值了,如果在接下来的程序中,没有给s赋新值就再调用s,由于s在内存中是随机值,就有可能指向重要地址,以致使系统崩溃,稳妥起见,如果delete之后程序还没结束,就将s的值赋为NULL。

补充

  内存的分配方式有以下四种:
(1)从静态存储区域分配,例如全局变量,这些内存在程序编译时就已分配,程序运行期间都存在;
(2)从栈上分配,例如局部变量,这些内存在函数结束时被自动释放;
(3)从堆上分配,例如malloc函数申请的内存,动态内存的生存期不会随着函数结束释放,由程序员决定;
(4)其他,例如常量、二进制代码等,这些内存在程序运行期间一直存在,程序结束后释放。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++动态分配内存的几种方法 的相关文章

  • Dapper 强类型查询返回默认对象值

    刚刚开始使用 Dapper 并喜欢它 我遇到了问题 它返回正确数量的对象 但它们的属性都有默认值 using var dbConnection Connection await dbConnection OpenAsync const st
  • 如何使用 C# 打印 pdf

    我在 C 应用程序中使用 进程 打印 pdf 文件 但是我无法获取打印状态 我发现可以通过 System management 和 System printing 与打印机 队列进行交互 我做了很多尝试 但都出错了使用这两个命名空间但无法打
  • ProtoBuf-net AsReference 需要 Activator.CreateInstance 中的公共构造函数吗?

    在我的两门课程中 看起来像这样 最少 using System using System Collections Generic using System Collections using System ComponentModel us
  • C++ 有像 Pascal 一样的“with”关键字吗?

    withPascal 中的关键字可用于快速访问记录的字段 有人知道 C 是否有类似的东西吗 前任 我有一个包含许多字段的指针 但我不想这样输入 if pointer gt field1 pointer gt field2 pointer g
  • .NET Windows 服务中调用 C# 的 wait 的 I/O 回调是否可以不阻塞?

    我知道在 ASP NET 中 当使用 wait 时工作线程会返回到池中 而 I O 发生在后台 这对于可扩展性非常有用 我的 Windows 服务是一个套接字服务器 它使用 Begin End 样式的异步套接字 I O 混合我的魔法 我知道
  • 从 .Net 将简单数据插入 Excel 文件的最简单方法

    我有一个 Excel 文件 大约有 10 列和 1 20 行 我需要插入 1 20 行包含各种数据元素 我想知道是否有一种方法可以将一些标签放入 Excel 文件中 以便可以找到并替换它们 将列标记为 名称 的东西 这样我就可以在代码中说
  • 为什么 LinkedList 通常比 List 慢?

    我开始在我的一些 C 算法中使用一些 LinkedList 而不是列表 希望能够加快速度 然而 我注意到他们只是感觉更慢 像任何优秀的开发人员一样 我认为我应该尽职调查并验证我的感受 所以我决定对一些简单的循环进行基准测试 我认为用一些随机
  • 浏览器收集哪些值作为回发数据?

    当页面被发送回服务器时 浏览器收集每个控件的当前值并将其粘贴到一个字符串中 然后 该回发数据通过 HTTP POST 发送回服务器 Q1 除了控件的 Text 属性和 SelectedIndexchanged 因此除了用户输入数据 之外 控
  • 隐形打开的弹出窗口

    第二天就解决这个问题 要重现 请创建新的 WPF 应用程序 xaml
  • 如何在 C++ 的子目录中创建文件?

    这是我的代码 如何在子目录联系人中创建文件 每次创建该文件时 它都会出现在与我的程序相同的目录中 int main ofstream myfile contacts myfile open a myfile close 在构造函数中指定完整
  • Windows 程序如何临时更改其时区?

    我写了一个函数来返回time t与给定日期的午夜相对应的值 当给定日期没有午夜时 它返回最早可用的时间 例如 当埃及进入夏令时时 这种情况就可能发生 今年 时间更改于 4 月 29 日晚上午夜生效 因此时钟直接从 23 59 转到 01 0
  • 使用 Microsoft Graph 创建用户

    如何使用 Microsoft graph 创建用户 因为我在保存过程中遇到了权限失败的问题 我确实有几个问题 在图中调用创建用户 API 将在哪里创建用户 是在 Azure AD 还是其他地方 我尝试通过传递 json 和必需的标头来调用创
  • 在 C++ 中处理音频缓冲区时,如何执行从 float -> double -> float 的转换

    我目前正在开发一个应用程序 其中音频样本帧在以下回调中进行处理 void Eav07AudioProcessor processBlock AudioSampleBuffer buffer for int channel 0 channel
  • 从 ef core 的子集合中删除一些项目

    我有一个父表和子表 其中父表与子表具有一对多关系 我想删除一些子项 并且希望父项的子集合反映该更改 如果我使用删除选定的子项RemoveRange 那么子集合不会更新 如果我使用Remove从子集合中删除子集合然后 显然 它不如使用效率高R
  • 在非指针变量和类成员上放置 new

    考虑以下示例 include
  • fscanf 和 EOF 中的否定扫描集

    我的文件中有一个以逗号分隔的字符串列表 姓名 1 姓名 2 姓名 3 我想跳过所有逗号来阅读这些名字 我写了以下循环 while true if fscanf file my string 1 break 然而 它总是比预期多执行一次 给定
  • Unity 2.0 和处理 IDisposable 类型(特别是使用 PerThreadLifetimeManager)

    我知道类似的问题被问过好几次 例如 here https stackoverflow com questions 987761 how do you reconcile idisposable and ioc here https stac
  • 获取大于某个数字的元素个数

    我正在尝试解决以下问题 数字被插入到容器中 每次插入数字时 我需要知道容器中有多少元素大于或等于当前插入的数字 我相信这两个操作都可以以对数复杂度完成 我的问题 C 库中有标准容器可以解决这个问题吗 我知道std multiset可以在对数
  • C# 中成员访问中的问号是什么意思?

    有人可以向我解释一下以下代码中会员访问中的问号是什么意思吗 它是标准 C 的一部分吗 尝试在 Xamarin Studio 中编译此文件时出现解析错误 this AnalyzerLoadFailed Invoke this new Anal
  • XmlDocument Save 使文件保持打开状态

    我有一个简单的 C 函数 可以创建一个基本的 XML 文件并保存 private void CreateXMlFile string Filename string Name string Company XmlDocument doc n

随机推荐

  • mysql drop记录_MySQL 在线惊心动魄的drop 千万记录表字段

    需要操作一个线上的表 添加字段 先查看磁盘占据大小以及记录数 500W记录 3 3G磁盘空间 hy 3306 orcl20 53 33 gt select table name table rows data length index le
  • 夜莺监控系统部署企业微信机器人告警

    前言 前面我们写了邮件的告警 现在我们来写下企业微信的机器人高级 Git仓库 wechatrobot sender 步骤 创建机器人 创建内部群 gt 鼠标右击 添加机器人 新建机器人 点击新创建一个机器人 创建机器人 输入昵称点击确定即可
  • Verilog 多路选择器(MUX),锁存器(Latch)推荐写法

    Veriog中二选一MUX推荐写法 always a b sel if sel 1 b1 z a else z b MUX为组合逻辑 用always来描述的时候 敏感变量列表中要包含在块中出现的所有变量 如上面代码中的 a b sel 如果
  • 使用Spring boot 构建知识图谱及简单Java GUI学习笔记

    使用Spring boot 构建知识图谱及简单Java GUI学习笔记 一 学习目的 开发一个关于房地产法律相关的智能问答系统 1 在简单Java GUI界面中实现一个科大讯飞语音调用及知识图谱API调用 反馈查询结果 实现一个简单的dem
  • 特征提取算法

    特征提取 1 背景 2 边界预处理 Moore boundary tracing algorithm Chain Codes Freeman Chain Codes slope chain codes SCCs minimum perime
  • 华为OD机试 - 竖直四子棋(Java)

    题目描述 竖直四子棋的棋盘是竖立起来的 双方轮流选择棋盘的一列下子 棋子因重力落到棋盘底部或者其他棋子之上 当一列的棋子放满时 无法再在这列上下子 一方的4个棋子横 竖或者斜方向连成一线时获胜 现给定一个棋盘和红蓝对弈双方的下子步骤 判断红
  • 与ag-Grid一起使用的Vue组件

    ag Grid Vue组件 ag Grid Vue Component 现场演示 Live Demo https www ag grid com https www ag grid com 用Bower安装 Install with Bow
  • VMware虚拟机安装Windows Server 2008 R2

    想必同学们已经开学了 也都进入了军训阶段吧 而很多计算机网络专业的同学们要开始接触到Windows Server了 这也是计算机网络技术专业的专业基础课程 想当年我们实训课学习使用的好像是2008版的 也不晓得现在各个学校会用到哪个版本实操
  • matlab做三次拉格朗日插值多项式_MATLAB的插值与拟合

    一 插值 什么是插值 首先看一下 百度百科的定义 在离散数据的基础上补插连续函数 使得这条连续曲线通过全部给定的离散数据点 从古到今 百度百科的定义一直 欲哭无泪 这是啥意思 简而言之就是 我现在有n个点对应的函数值 但是我想知道 在这个区
  • 科研经验干货帖(包含文献阅读方法、论文书写和投递技巧以及回复审稿意见的注意事项)

    科研经验干货帖 科研小白自己整理科研大神国奖师兄的经验分享笔记 以便自己查看 科研的流程 发现问题 论文idea从这里开始 想要多产生idea要多阅读领域内最好的文章 重现他们的实验 然后解决这些文章存在的问题 因此后面会讲如何阅读文献 核
  • 用matlab画y=cos(x)函数(记录学习过程)

    初始代码 创建横坐标的数据点 x linspace 0 12 1000 计算纵坐标的数据点 y cos x 绘制曲线 plot x y r 设置坐标轴范围 xlim 0 12 ylim 1 1 给y坐标着色 set gca YColor g
  • python爬虫入门心得体会,python爬虫入门和实例

    爬虫不外乎是为了获取网络上的信息 要取得信息 你就得给给服务器发请求 然后服务器把信息发给你 这一步一般较为简单 服务器发给你的一般是一个html文件 拿到文件后 你可能会觉得这是什么乱七八糟的东西 怎么都看不懂 我觉得对于一个非计算机系的
  • 叉积

    叉积的计算是线段方法的核心 考虑如图33 1 a 所示的向量p1和p2 我们可以把叉积解释为由点 0 0 p1 p2 和 p1 p2 x1 x2 y1 y2 所构成的平行四边形的有向面积 另一种与之等价但更有效的叉积定义方式是将之看做矩阵行
  • 《算法不好玩》专题三:循环不变量

    3 1循环不变量 循环不变量 在循环的过程中保持不变的性质 循环不变式主要用来帮助我们理解算法的正确性 关于循环不变式 我们必须证明三条性质 初始化 循环的第一次迭代之前 它为真 保持 如果循环的某次迭代之前它为真 那么下次迭代之前它仍为真
  • 压缩图片网站

    https tinypng com 压缩图片网站
  • java入职写不出代码_各位程序员是怎么度过看懂代码但写不出来的时期?

    说实话 我自己就没经历过这样的时期 说看得懂代码的 大概是很少看开源代码 想看懂不仅自己水平要过硬 还要花挺大的精力把前前后后的东西都做充分的研究 我自己总是会有自己写一个功能很有思路 但是搞懂别人同样功能代码却感觉很费力的体验 我猜测题主
  • safari ajax timeout,Safari ajax提交表单无响应?

    两个项目均遇到了Safari ajax提交无响应了 表单里有text file字段 file字段用于上传封面图片 创建的时候没问题 当修改的时候 我不想修改封面图 只修改内容 结果Safari提交后无响应 一直在转圈 到最后超时提示 Fai
  • 2020年,Java 开发者必须了解的 16 个Java 顶级开源项目

    2020年 值得你关注的16个Java 开源项目 本文已经收录自笔者开源的 JavaGuide https github com Snailclimb JavaGuide Java学习 面试指南 一份涵盖大部分Java程序员所需要掌握的核心
  • linux qt读写文件,QT 文件读写操作

    include include 1 打开文件 QFile f fn fn可以是一个相对路径或绝对路径 f open IO 一般不要IO ReadWrite 很容易出现赃数据 如果要在文件的后面添加内容要IO WriteOnly IO App
  • C/C++动态分配内存的几种方法

    使用C C 编程时 会经常动态分配内存 以便合理使用内存 本文主要讲述动态内存分配的几种方法及一些原理 理解不深刻之处欢迎指教 引言 为什么要进行动态内存分配 以数组为例 数组元素在内存中存储的地址是连续的 声明一个数组后 该数组需要的内存