内存地产风云录: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)

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

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

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

相关文章

绿联 安装SeaTable在线协同表格

绿联 安装SeaTable在线协同表格 SeaTable 是一款以在线协同表格为基础的新型企业数字化平台。它支持“文件”、“图片”、“单选项”、“协作人”、“计算公式”等丰富的数据类型&#xff0c;帮助你用表格的形式来方便的组织和管理各类信息。在表格基础上&#xff0c;它支持自…

MySQL相关问题快问快答

我写这篇文章的目的只有一个&#xff1a;通过这些问题来帮助我去将我脑子里的MySQL脑图给巩固熟悉&#xff0c;通过回答这些问题&#xff0c;让我对脑子里的MySQL知识有更深的印象&#xff0c;当什么时候我的MySQL脑图不熟的时候&#xff0c;我就可以拿这篇文章来去巩固一下&am…

构建第一个ArkTS之基本语法概述

在初步了解了ArkTS语言之后&#xff0c;我们以一个具体的示例来说明ArkTS的基本组成。如下图所示&#xff0c;当开发者点击按钮时&#xff0c;文本内容从“Hello World”变为“Hello ArkUI”。 图1 示例效果图 本示例中&#xff0c;ArkTS的基本组成如下所示。 图2 ArkTS的基本…

[大模型]Qwen1.5-4B-Chat WebDemo 部署

Qwen1.5-4B-Chat WebDemo 部署 Qwen1.5 介绍 Qwen1.5 是 Qwen2 的测试版&#xff0c;Qwen1.5 是基于 transformer 的 decoder-only 语言模型&#xff0c;已在大量数据上进行了预训练。与之前发布的 Qwen 相比&#xff0c;Qwen1.5 的改进包括 6 种模型大小&#xff0c;包括 0.…

回溯算法2s总结

8.回溯算法 回溯算法理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案 回溯法解决的问题 回溯法&#xff0c;一…

买随身WiFi快存好❗随身WiFi真的靠谱吗?随身WiFi推荐测评!随身WiFi哪个牌子靠谱?

选购随身WiFi避免众多波折&#xff0c;这篇随身WiFi购买指南&#xff0c;务请珍藏于心&#xff0c; 以备不时之需&#xff01;选购随身WiFi的三大避坑指南&#xff1a;1、基站信号稳为先&#xff1a;选择前务必细查所在地基站信 号&#xff0c;确保网络畅通无阻。 2、正规认证…

必应bing搜索广告推广国内能开户吗?

随着互联网广告市场的不断进化和细分化&#xff0c;必应Bing搜索广告已逐渐成为中国企业拓展国内市场、精准触达目标客户的重要渠道之一。2024年&#xff0c;必应Bing在国内市场的进一步布局&#xff0c;不仅彰显了其对本土企业的强大吸引力&#xff0c;更带来了全新的开户政策…

Go——面向对象

一. 匿名字段 go支持只提供类型而不写字段名的方式&#xff0c;也就是匿名字段&#xff0c;也称为嵌入字段。 同名字段的情况 所以自定义类型和内置类型都可以作为匿名字段使用 指针类型匿名字段 二.接口 接口定义了一个对象的行为规范&#xff0c;但是定义规范不实现&#xff…

利用免费AI开源引擎:实现图像识别技术在多主体检测中的应用|识别万物|本地化部署

在当今快速发展的图像处理领域&#xff0c;图像主体检测技术已成为提升图像分析效率和精度的关键工具。该技术能够自动识别和定位图像中的一个或多个主要对象&#xff0c;并提供其具体的位置坐标和分类标签。这不仅为图像编辑和优化提供了便利&#xff0c;也为后续的图像识别任…

C-开发 visual Studio扩展插件介绍-格式化插件Xaml Styler、CSharpier介绍(扩展插件安装方法)

C#开发 visual Studio扩展插件介绍 扩展插件安装方法Xaml StylerCSharpier 提高C#开发效率常用的插件 扩展插件安装方法 菜单栏点击“扩展”→“管理扩展”。 打开扩展页面 右上角搜索需要安装的插件&#xff0c;然后点击下载 安装完成后&#xff0c;根据提示关闭VS进行安…

selenium绕过网站检测的方法

使用selenium打开如下网站&#xff0c;进行检测&#xff0c;代码如下&#xff1a; from selenium import webdriver import timedriver webdriver.Chrome() driver.get(https://bot.sannysoft.com/) time.sleep(60)发现webdriver被检测到了 在这里可使用一个selenium提供的插…

【MATLAB源码-第7期】基于matlab的8PSK的实际误码率BER和理论误码率BER对比仿真。

1、算法描述 8PSK (8 Phase Shift Keying 8移相键控) 是一种相位调制算法。相位调制&#xff08;调相&#xff09;是频率调制&#xff08;调频&#xff09;的一种演变&#xff0c;载波的相位被调整用于把数字信息的比特编码到每一次相位改变&#xff08;相移&#xff09;。&quo…

是时候将 DevOps 可见性扩展到网络边缘了

尽管部署前运行了大量测试&#xff0c;但在部署应用程序后&#xff0c;性能问题经常让 DevOps 团队感到困惑。经过进一步调查&#xff0c;最常被忽视的问题是应用程序本身的分布式特性。从多个位置访问应用程序的最终用户永远不会拥有相同水平的互联网服务&#xff0c;因此在纽…

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

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

【大语言模型】基础:如何处理文章,向量化与BoW

词袋模型&#xff08;BoW&#xff09;是自然语言处理&#xff08;NLP&#xff09;和机器学习中一种简单而广泛使用的文本表示方法。它将文本文档转换为数值特征向量&#xff0c;使得可以对文本数据执行数学和统计操作。词袋模型将文本视为无序的单词集合&#xff08;或“袋”&a…

【电控笔记0】稳定度判断

简要概括 现控:原理虚轴,稳定度越高 自控:相位裕度PM 增益裕度GM 开环传函 不稳定条件判断

Proteus 8 的使用记录

创建仿真文件 新建文件&#xff1a;默认下一步&#xff0c;至完成创建。 功能选择如图&#xff1a; 放置器件 常用元器件名称 keywords 常用51单片机 AT89C52 晶振 CRYSTAL 电阻 RES 排阻 RESPACK-8 瓷片电容 CAP 电解电容 CAP-ELEC 单刀单掷开关 S…

【教学类-52-01】20240411动物数独(4宫格)

作品展示 背景需求&#xff1a; 一、下载图片 PS修图&#xff08;图片长宽一样&#xff0c;把动物图片上下拉长&#xff09; 二、数独结构分析&#xff1a; 1、这是一个四宫格的数独题&#xff0c; 2、将1234换成了四种小动物图片。 于是我去找到原来做过的一个代码&#xf…

day05-java面向对象(上)

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

web安全-SSH私钥泄露

发现主机 netdiscover -r 192.168.164.0 扫描端口 看到开放80和31337端口都为http服务 浏览器访问测试 查看80端口和31337端口网页和源代码并无发现有用信息 目录扫描 扫描出80端口并无有用信息 扫描31337端口 发现敏感文件robots.txt和目录.ssh 访问敏感文件和目录 /.ss…