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

news2025/1/12 10:45:33

欢迎来到白刘的领域   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)

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学知识——欧几里得算法(辗转相除法)

欧几里得算法用来求最大公约数 int gcd(int a, int b) {if(b 0) return a;else return gcd(b, a % b); } 例题&#xff1a;洛谷p1029 #include<iostream>using namespace std;#define int long long #define endl \nint x, y; int ans;int gcd(int x, int y) {if(y 0)…

C语言世界上最详细自定义类型:联合和枚举

前言&#xff1a; hello! 大家好&#xff0c;我是小陈&#xff0c;今天给大家带来一篇联合和枚举的博客&#xff01;&#xff01;&#xff01; 1.联合体类型的声明 像结构体⼀样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。 但是编译…

vue 文件预览

<template><div><p>打开新页面预览文件</p><div v-for"(item,index) in list" :key"index"><el-link type"primary" click"handleOpen(item.url)">{{item.name}}</el-link></div><…

Day 24 回溯理论基础 77. 组合

回溯理论基础 ​ 在递归中已经提到过了&#xff0c;回溯是递归的副产品&#xff0c;只要有递归就会有回溯&#xff1b; ​ 回溯法本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出需要的答案&#xff0c;并不是什么高效的算法&#xff1b; ​ 不高效但又不得不用&am…

Python3.7编程之病毒

基础篇 什么是病毒 病毒&#xff0c;指的是一些通过非法手段获取系统的一些权限&#xff0c;然后进行破坏或者盗取。 病毒分为两类&#xff1a; 1、破坏型 这类病毒往往会将系统弄的乱七八糟&#xff0c;比如把你的U盘删光&#xff0c;把你的系统背景调成黑客图片&#xff0c…

数据结构:线性表————单链表专题

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

最简洁的Docker环境配置

Docker环境配置 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Mac、Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不…

YOLOv8打印模型结构配置信息并查看网络模型详细参数:参数量、计算量(GFLOPS)

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

docker-compose yaml指定具体容器网桥ip网段subnet

docker-compose 启动yaml有时可能的容器网段与宿主机的ip冲突导致宿主机上不了网&#xff0c;这时候可以更改yaml指定subnet 宿主机内网一般是192**&#xff0c;这时候容器可以指定172* version: 3.9 services:coredns:image: coredns/coredns:1.10.0container_name: coredns…

【面试必备】MySQL索引是什么?怎么设计索引?

在后端面试中&#xff0c;MySQL的索引是一个常见问题&#xff0c;尤其是最近掀起了去Oracle的风向。作为一个很宽泛的面试题&#xff0c;不仅考验对MySQL整体知识的了解&#xff0c;也方便面试官随着我们的回答逐渐往下延伸问题。众所周知&#xff0c;面试问题的答案&#xff0…

【力扣题】关于单链表和数组习题

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

使用MATLAB的cylinder函数生成圆柱体及其他应用

cylinder 函数是 MATLAB 中的一个内置函数&#xff0c;用于生成表示圆柱体表面的坐标点。这些坐标点可以用于绘制三维图形&#xff0c;如使用 surf 或 mesh 函数进行可视化。 cylinder函数生成单位圆柱体的x、y和z坐标。您可以使用surf或mesh来绘制圆柱形对象&#xff0c;或者…

1233. 全球变暖---BFS

目录 1233. 全球变暖 输入格式 输出格式 数据范围 输入样例1&#xff1a; 输出样例1&#xff1a; 输入样例2&#xff1a; 输出样例2&#xff1a; 思路&#xff1a; 宽搜BFS 模板&#xff1a; 代码&#xff1a; 运行结果&#xff1a; 1233. 全球变暖 你有一张某海域 NN…

Mongo 报错 Can‘t canonicalize query: BadValue $in needs an array

一、遇到的问题 Mongo in查询 [ UserId > array($in>$userIds)] $userIds数组不是连续索引&#xff0c;报错Cant canonicalize query: BadValue $in needs an array 二、解决 array_values($userIds) 重新索引一下变成连续索引即可。 Mongo in查询的数组要是连续索…

WEB3.0:互联网的下一阶段

随着互联网的发展&#xff0c;WEB3.0时代正在逐步到来。本文将深入探讨WEB3.0的定义、特点、技术应用以及未来展望&#xff0c;为读者带来全新的思考。 一、什么是WEB3.0&#xff1f; WEB3.0可以被理解为互联网发展的下一阶段&#xff0c;是当前WEB2.0的升级版。相较于2.0时代…

react状态管理库---zustand

一个简单的&#xff0c;快速的状态管理解决方案&#xff0c;api设计基于函数式和hooks 安装&#xff1a; npm install zustand 基础使用 让我们实现一个非常简单的计数器案例完成我们的第一个store 1- 创建一个counterStore create( ) 有三个参数&#xff1a;函数、布尔值…

[C++][算法基础]模拟散列表(哈希表)

维护一个集合&#xff0c;支持如下几种操作&#xff1a; I x&#xff0c;插入一个整数 x&#xff1b;Q x&#xff0c;询问整数 x 是否在集合中出现过&#xff1b; 现在要进行 N 次操作&#xff0c;对于每个询问操作输出对应的结果。 输入格式 第一行包含整数 N&#xff0c;…

类和对象【一】类和对象简介

文章目录 C的类与C语言结构体的区别【引入类】类的定义类体中的成员函数的实现类中的访问限定符C中class和struct的区别 类的作用域类的实例化类中成员的存储位置类的大小 C的类与C语言结构体的区别【引入类】 类里面不仅可以定义变量还可以定义函数 例 类具有封装性【将在该…

c++ 指针总结

概述 内存地址 在计算机内存中&#xff0c;每个存储单元都有一个唯一的地址(内存编号)。通俗理解&#xff0c;内存就是房间&#xff0c;地址就是门牌号 指针和指针变量 指针&#xff08;Pointer&#xff09;是一种特殊的变量类型&#xff0c;它用于存储内存地址。指针的实质…

libcurl 简单实用

LibCurl是一个开源的免费的多协议数据传输开源库&#xff0c;该框架具备跨平台性&#xff0c;开源免费&#xff0c;并提供了包括HTTP、FTP、SMTP、POP3等协议的功能&#xff0c;使用libcurl可以方便地进行网络数据传输操作&#xff0c;如发送HTTP请求、下载文件、发送电子邮件等…