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

内存地产风云录:malloc、free、calloc、realloc演绎动态内存世界的楼盘开发与交易大戏

欢迎来到白刘的领域   Miracle_86.-CSDN博客

系列专栏  C语言知识

先赞后看,已成习惯

   创作不易,多多支持!

在这个波澜壮阔的内存地产世界中,malloc、free、calloc和realloc四位主角,共同演绎着一场场精彩绝伦的楼盘开发与交易大戏。

目录​​​​​​​

一、为什么要有动态内存分配 

二、malloc和free

2.1 malloc —— 购买土地

2.2 free —— 出售土地 

三、calloc和realloc

3.1 calloc —— 批量购买并初始化土地

3.2 realloc —— 调整土地大小

四、常见的动态内存错误

4.1 对NULL指针解引用

4.2 对动态内存开辟空间的越界访问

4.3 对非动态开辟内存进行free释放

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

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

4.6 忘记释放(内存泄漏)

五、柔性数组

5.1 柔性数组的特点

5.2 柔性数组的使用 

六、总结C/C++中程序内存区域划分


一、为什么要有动态内存分配 

我们已经掌握的内存开辟方法有:

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

 但是上述的开辟方法有两个缺点:

1. 开辟的空间大小是有限的。

2. 数组在开辟的时候,必须声明数组的长度,数组空间一旦确定大小就不能调整。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

所以在C语言中,我们引入了动态内存开辟,可以让程序员自己申请和释放空间,就比较灵活了。

二、malloc和free

如果我们将内存比作地产,那mallocfree就可以非常恰当地比作:购买土地出售土地。

2.1 malloc —— 购买土地

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

void* malloc(size_t size);

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

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

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

3. 返回值类型为void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候程序员自己确定。

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

2.2 free —— 出售土地 

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

void free(void* ptr);

free函数用来释放动态开辟的内存。

1. 如果参数ptr指向的空间不是动态开辟的,那free的行为是未定义的。

2. 如果参数ptr是NULL指针,则什么也不做。

malloc和free都包含在<stdlib.h>头文件中。

eg:

#include <stdio.h>
#include <stdlib.h>
int main()
{int num = 0;scanf("%d", &num);int arr[num] = { 0 };int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int));if (NULL != ptr)//判断ptr指针是否为空{int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0;}}free(ptr);//释放ptr所指向的动态内存ptr = NULL;//是否有必要?return 0;
}

 首先,定义了一个整数变量num并初始化为0。然后使用scanf函数从标准输入读取一个整数,并存储在num中。然后,声明了一个长度为num的整数数组arr,并将其所有元素初始化为0。注意,在C99标准之前,这种变长数组(VLA)是不被允许的。但在C99及之后的版本中,这是合法的。变长数组在之前我们也有所讲过:

C语言中的百宝箱——数组(2)-CSDN博客

然后我们定义了一个整数指针ptr并初始化为NULL。 之后使用malloc函数动态分配了num个整数大小的内存,并将返回的指针赋值给ptr。if语句首先检查ptr是否为NULL,以确保内存分配成功。如果成功,则使用一个循环将动态分配的内存的每一个位置初始化为0。紧接着我们使用free函数释放ptr所指向的内存,以避免内存泄漏。

最后我们为什么将ptr设置为空指针,因为此时它是个野指针!如果我们接下来对其操作,将造成严重的后果,再一个就是提高了代码可读性,设置为空指针,提示这块内存已经被释放了。

三、calloc和realloc

callocrealloc也可以有一个比较恰当的比喻:批量购买并且初始化土地(在土地上盖房子)调整土地大小。

3.1 calloc —— 批量购买并初始化土地

C语言中还有一个函数,也是用来动态内存分配,它就是calloc。原型如下:

void* calloc(size_t num, size_t size);

1. 函数的功能是为num个大小为size的元素开辟一块空间,并且把每块空间都初始化为0。

2. 与malloc的区别就是:calloc会在返回地址之前,将申请空间的每个字节都初始化为0。

eg:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}}free(p);p = NULL;return 0;
}

 运行结果:

所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

3.2 realloc —— 调整土地大小

realloc的出现,让动态内存管理更加灵活。

有的时候,我们会觉得申请的空间太小了,有的时候又觉得太大了,那为了合理的内存,我们一定会对内存的大小做灵活的调整,而realloc的作用就是可以做到对动态开辟内存的大小做调整。

函数原型:

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

1. ptr是要调整的地址,size是调整后的新大小。 

2. 返回值为调整之后的内存起始位置。

3. 这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

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

情况1:原有空间之后有足够大的空间。

情况2:原有空间之后没有足够大的空间。

当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

当是情况2的时候,原有空间之后没有足够的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

由于上述两种情况,我们在realloc的使用就要注意一些。

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = (int*)malloc(100);if (ptr != NULL){//业务处理}else{return 1;}//扩展容量//代码1 - 直接将realloc的返回值放到ptr中ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中int* p = NULL;p = realloc(ptr, 1000);if (p != NULL){ptr = p;}//业务处理free(ptr);return 0;
}

代码1:

这种直接使用realloc的方式是可行的,但需要注意以下几点:

返回值检查:如果realloc函数调用失败,它会返回NULL。这时,原来的内存块(由ptr指向)也不会被释放,所以需要确保在将realloc的返回值赋给ptr之前检查其返回值是否为NULL

内存泄漏:如果realloc失败并返回NULL,而我们又没有保存原来的ptr的值,那么将失去对原始内存块的引用,从而导致内存泄漏。

代码2:

这种方式更加安全,因为它首先创建了一个新的指针p来保存realloc的返回值。如果realloc成功,p将指向新的内存块,然后你可以安全地将p的值赋给ptr。如果realloc失败,p将为NULL,但ptr仍然指向原来的内存块,因此不会发生内存泄漏。

由于上述代码,我们就不得不简单介绍几个常见的动态内存的错误。

四、常见的动态内存错误

4.1 对NULL指针解引用

void test()
{int* p = (int*)malloc(INT_MAX / 4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

4.2 对动态内存开辟空间的越界访问

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}

4.3 对非动态开辟内存进行free释放

void test()
{int a = 10;int* p = &a;free(p);//ok?
}

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

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

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

void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

4.6 忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

五、柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫做“柔性数组”成员。

eg:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;

有的编译器可能会报错,可以改成:

typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;

5.1 柔性数组的特点

1. 结构体中的柔性数组前面至少有一个成员。

2. sizeof返回结构体时不包括柔性数组的大小。

3. 包含柔性数组的结构体用malloc函数进行动态开辟,并且分配的内存大小应该大于结构体的大小,以适应柔性数组的大小。

5.2 柔性数组的使用 

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  // 定义一个包含柔性数组成员的结构体  
typedef struct {  int count;  double data[]; // 柔性数组成员  
} FlexArray;  int main() {  int array_size = 10; // 假设我们想要一个大小为10的数组  size_t struct_size = sizeof(FlexArray) - sizeof(double[0]); // 计算结构体的固定部分大小  size_t total_size = struct_size + sizeof(double) * array_size; // 计算总大小  // 使用malloc分配内存  FlexArray *p = (FlexArray *)malloc(total_size);  if (p == NULL) {  perror("Memory allocation failed");  return EXIT_FAILURE;  }  // 初始化结构体  p->count = array_size;  for (int i = 0; i < array_size; ++i) {  p->data[i] = i * 1.0; // 假设我们为数组填充一些值  }  // 使用结构体...  for (int i = 0; i < p->count; ++i) {  printf("%f\n", p->data[i]);  }  // 释放内存  free(p);  return 0;  
}

在这个例子中,我们首先计算了结构体的固定部分大小(不包括柔性数组成员),然后加上柔性数组所需的大小,计算出总大小。malloc函数被用来分配所需的总内存大小。

注意,我们在计算结构体固定部分大小时使用了sizeof(double[0]),这是为了确保在计算时不包括柔性数组成员。这个技巧依赖于sizeof对于数组类型返回的是数组的总大小,即使数组的大小是0。

另外,在使用柔性数组成员时,要确保不要试图对结构体使用sizeof来获取完整大小,因为这会返回不包含柔性数组成员的大小。总是根据你的需要动态地计算并分配内存。

最后,别忘了在使用完分配的内存后调用free函数来释放它,以避免内存泄漏。

六、总结C/C++中程序内存区域划分

代码区(Code Area 或 Text Area)

  • 也称为文本段或代码段,它存放程序执行的二进制代码,包括机器指令。这部分内存是只读的,以防止程序意外地修改了它的指令。
  • 编译后的机器码(CPU执行的指令)就放在这一部分内存中。

全局/静态存储区(Global/Static Storage Area)

  • 全局变量和静态变量的存储区域。全局变量包括在函数外部定义的变量,而静态变量包括在函数内部使用static关键字定义的变量以及全局静态变量。
  • 这部分内存的生命周期是整个程序的执行期间。

堆区(Heap Area)

  • 动态内存分配的区域,通常使用malloccallocrealloc在C中分配内存,或者在C++中使用new操作符分配。
  • 程序员负责在不再需要时释放这部分内存,否则会导致内存泄漏。

栈区(Stack Area)

  • 由编译器自动分配和释放,存放函数的参数值、局部变量等。其操作方式类似于数据结构中的栈。
  • 每次函数调用时,都会在栈上为其分配一块内存,用于存储函数的局部变量等。当函数返回时,这块内存会被自动释放。

相关文章:

内存地产风云录:malloc、free、calloc、realloc演绎动态内存世界的楼盘开发与交易大戏

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 在这个波澜壮阔的内存地产世界中&#xff0c;malloc、free、calloc和realloc四位主角&#xff0c;共同演绎着一场场精彩绝伦的楼盘开…...

个人博客项目笔记_05

1. ThreadLocal内存泄漏 ThreadLocal 内存泄漏是指由于没有及时清理 ThreadLocal 实例所存储的数据&#xff0c;导致这些数据在线程池或长时间运行的应用中累积过多&#xff0c;最终导致内存占用过高的情况。 内存泄漏通常发生在以下情况下&#xff1a; 线程池场景下的 ThreadL…...

基础知识点全覆盖(1)

Python基础知识点 1.基本语句 1.注释 方便阅读和调试代码注释的方法有行注释和块注释 1.行注释 行注释以 **# **开头 # 这是单行注释2.块注释 块注释以多个 #、三单引号或三双引号(注意: 基于英文输入状态下的标点符号) # 类 # 似 # 于 # 多 # 行 # 效 # 果 这就是多行注释…...

异常处理java

在Java中&#xff0c;异常处理可以使用"throws"关键字或者"try-catch"语句。这两种方法有不同的用途和适用场景。 "throws"关键字: 在方法声明中使用"throws"关键字&#xff0c;表示该方法可能会抛出异常&#xff0c;但是并不立即处理…...

个人博客项目_09

1. 归档文章列表 1.1 接口说明 接口url&#xff1a;/articles 请求方式&#xff1a;POST 请求参数&#xff1a; 参数名称参数类型说明yearstring年monthstring月 返回数据&#xff1a; {"success": true, "code": 200, "msg": "succ…...

【2024年MathorCup数模竞赛】C题赛题与解题思路

2024年MathorCup数模竞赛C题 题目 物流网络分拣中心货量预测及人员排班背景求解问题 解题思路问题一问题二问题三问题四 本次竞赛的C题是对物流网络分拣中心的货量预测及人员排班问题进行规划。整个问题可以分为两个部分&#xff0c;一是对时间序列进行预测&#xff0c;二是对人…...

蓝桥杯省赛冲刺(3)广度优先搜索

广度优先搜索&#xff08;Breadth-First Search, BFS&#xff09;是一种在图或树等非线性数据结构中遍历节点的算法&#xff0c;它从起始节点开始&#xff0c;按层级逐步向外扩展&#xff0c;即先访问离起始节点最近的节点&#xff0c;再访问这些节点的邻居&#xff0c;然后是邻…...

网页内容生成图片,这18般武艺你会几种呢?

前言 关于【SSD系列】&#xff1a; 前端一些有意思的内容&#xff0c;旨在3-10分钟里&#xff0c; 500-1000字&#xff0c;有所获&#xff0c;又不为所累。 网页截图&#xff0c;windows内置了快捷命令和软件&#xff0c;chrome开发者工具也能一键截图&#xff0c;html2canva…...

pytest的时候输出一个F后面跟很多绿色的点解读

使用pytest来测试pyramid和kotti项目&#xff0c;在kotti项目测试的时候&#xff0c;输出一个F后面跟很多绿色的点&#xff0c;是什么意思呢&#xff1f; 原来在使用pytest进行测试时&#xff0c;输出中的“F”代表一个失败的测试&#xff08;Failed&#xff09;&#xff0c;而…...

算法打卡day33

今日任务&#xff1a; 1&#xff09;509. 斐波那契数 2&#xff09;70. 爬楼梯 3&#xff09;746.使用最小花费爬楼梯 509. 斐波那契数 题目链接&#xff1a;509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 斐波那契数&#xff0c;通常用 F(n) 表示&#xff0c;形成…...

《疯狂java讲义》Java AWT图形化编程中文显示

《疯狂java讲义》第六版第十一章AWT中文没有办法显示问题解决 VM Options设置为-Dfile.encodinggbk 需要增加变量 或者这边直接设置gbk 此外如果用swing 就不会产生这个问题了。...

Python3 标准库,API文档链接

一、标准库 即当你安装python3 后就自己携带的一些已经提供好的工具模块&#xff0c;工具类&#xff0c;可以专门用来某一类相关问题&#xff0c;达到辅助日常工作或者个人想法的一些成品库 类似的 C ,Java 等等也都有自己的标准库和使用文档 常见的一些&#xff1a; os 模块…...

【Web】CTFSHOW-ThinkPHP5-6反序列化刷题记录(全)

目录 web611 web612 web613-622 web623 web624-626 纯记录exp&#xff0c;链子不作赘述 web611 具体分析&#xff1a; ThinkPHP-Vuln/ThinkPHP5/ThinkPHP5.1.X反序列化利用链.md at master Mochazz/ThinkPHP-Vuln GitHub 题目直接给了反序列化入口 exp: <?ph…...

AR智能眼镜方案_MTK平台安卓主板芯片|光学解决方案

AR眼镜作为一种引人注目的创新产品&#xff0c;其芯片、显示屏和光学方案是决定整机成本和性能的关键因素。在这篇文章中&#xff0c;我们将探讨AR眼镜的关键技术&#xff0c;并介绍一种高性能的AR眼镜方案&#xff0c;旨在为用户带来卓越的体验。 AR眼镜的芯片选型至关重要。一…...

Android网络抓包--Charles

一、Android抓包方式 对Https降级进行抓包&#xff0c;降级成Http使用抓包工具对Https进行抓包 二、常用的抓包工具 wireshark&#xff1a;侧重于TCP、UDP传输层&#xff0c;HTTP/HTTPS也能抓包&#xff0c;但不能解密HTTPS报文。比较复杂fiddler&#xff1a;支持HTTP/HTTPS…...

【LeetCode热题100】238. 除自身以外数组的乘积(数组)

一.题目要求 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 **不要使用除法&#xff0c;**且在…...

《哈迪斯》自带的Lua解释器是哪个版本?

玩过《哈迪斯》&#xff08;英文名&#xff1a;Hades&#xff09;吗&#xff1f;最近在研究怎么给这款游戏做MOD&#xff0c;想把它的振动体验升级到更高品质的RichTap。N站下载了一些别人做的MOD&#xff0c;发现很多都基于相同的格式&#xff0c;均是对游戏.sjon文件或.lua文…...

Java内存泄漏内存溢出

1.定义 OOM内存溢出是指应用程序尝试使用更多内存资源&#xff0c;而系统无足够的内存&#xff0c;导致程序崩溃。 内存泄漏是指应用程序中分配的内存未能被正确释放&#xff0c;导致系统中的可用内存逐渐减少。 2.内存泄漏的原因 可能包括对象引用未被释放、缓存未被清理等。 …...

【springboot】项目启动时打印全部接口方法

方法&#xff1a;在你springboot项目的基础上&#xff0c;创建下面的类&#xff1a; package com.llq.wahaha.listener;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework…...

单例19c RMAN数据迁移方案

一、环境说明 源库 目标库 IP 192.168.37.200 192.168.37.202 系统版本 RedHat 7.9 RedHat 7.9 数据库版本 19.3.0.0.0 19.3.0.0.0 SID beg beg hostname beg rman 数据量 1353M 说明:源库已经创建数据库实例&#xff0c;并且存在用户kk和他创建的表空间…...

05—面向对象(上)

一、面向对象编程 1、类和对象 &#xff08;1&#xff09;什么是类 类是一类具有相同特性的事物的抽象描述&#xff0c;是一组相关属性和行为的集合。 属性&#xff1a;就是该事物的状态信息。行为&#xff1a;就是在你这个程序中&#xff0c;该状态信息要做什么操作&#x…...

【LeetCode热题100】【链表】两数相加

题目链接&#xff1a;2. 两数相加 - 力扣&#xff08;LeetCode&#xff09; 基本思路同&#xff1a;【leetcode】大数相加-CSDN博客 数值的位置已经倒过来了&#xff0c;用一个进位记录进位&#xff0c;用一个数记录和&#xff0c;链表到空了就当成0 class Solution { publi…...

Linux命令学习—linux 的硬件管理

1.1、linux 的硬件管理 1.1.1、计算机的硬件管理 在 linux 下&#xff0c;计算机所有设备是以文件的形势存在的。 在 linux 下查看硬件信息 ①、lspci 列出所有的 PCI 设备 ②、fdisk -l 查看存储设备信息 ③、查看/proc 目录下相应的文件来查看一些设备信息 cat /proc/cp…...

通讯录项目(用c语言实现)

一.什么是通讯录 通讯录是一种用于存储联系人信息的工具或应用程序。它是一种电子化的地址簿&#xff0c;用于记录和管理个人、机构或组织的联系方式&#xff0c;如姓名、电话号码、电子邮件地址和邮寄地址等。通讯录的目的是方便用户在需要时查找和联系他人。 通讯录通常以列…...

让大模型落地有“技”可循

“2018年&#xff0c;随着Transformer预训练模型的兴起&#xff0c;自然语言处理&#xff08;NLP&#xff09;学术圈中形成了一个主流观点——NLP领域的不同技术方向&#xff0c;如文本分类、文本匹配、序列标注等&#xff0c;最终都会被归结到文本生成这一核心任务之下。”这是…...

java:字符集和字符流

字符集 规定了字符和二进制之间对应关系的一张表 字节是计算机最基本的存储单位 字符则是通过字符组成和编码而成的文本 常见字符集 1,ASCII字符集 基础字符编码标准,包含128个字符,只包括英文字母,数字和一些常见的符号 一个字节表示一个字符 所有的字符集均兼容ASCII…...

Java常见的设计模式

Java常见的设计模式 工厂模式&#xff08;Factory Pattern&#xff09;单例模式&#xff08;Singleton Pattern&#xff09;代理模式模式&#xff08;Proxy Pattern&#xff09;适配器模式&#xff08;Adapter Pattern&#xff09;观察者模式&#xff08;Observer Pattern&…...

Oracle 19c RAC集群相关日志

1.DB日志&#xff08;数据库日志&#xff09; Redo Log&#xff08;重做日志&#xff09;&#xff1a; 在Oracle数据库中&#xff0c;重做日志记录了数据库发生的所有修改操作&#xff0c;包括数据的插入&#xff0c;更新和删除。在RAC的环境中&#xff0c;每个实例都有自己的重…...

TR4 - Transformer中的多头注意力机制

目录 前言自注意力机制Self-Attention层的具体机制Self-Attention 矩阵计算 多头注意力机制例子解析 代码实现总结与心得体会 前言 多头注意力机制可以说是Transformer中最主要的模块&#xff0c;没有之一。这次我们来仔细分析一下注意力机制与多头注意力机制。 自注意力机制…...

three.js跟着教程实现VR效果(四)

参照教程&#xff1a;https://juejin.cn/post/6973865268426571784&#xff08;作者&#xff1a;大帅老猿&#xff09; 1.WebGD3D引擎 用three.js &#xff08;1&#xff09;使用立方体6面图 camera放到 立方体的中间 like “回” 让贴图向内翻转 &#xff08;2&#xff09;使…...