c语言动态内存分配

embedded/2024/9/19 21:39:38/ 标签: c语言, 算法, 开发语言

前言

我们已经掌握的内存开辟⽅式有:

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

但是上述的开辟空间的⽅式有两个特点:
空间开辟⼤⼩是固定的。
• 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
道,那数组的编译时开辟空间的⽅式就不能满⾜了。

C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。


一、malloc和free

1.1malloc

c语言提供了一个动态分配内存的函数(需要头文件#include<stdlib.h>)

void* malloc (size_t size);

我们可以注意到,他返回的是void*,因为这块空间是帮你开辟的,不知道你用来存放什么类型的数据,所以他自动存为了void*,方便你以后的各种操作,比如强转成float*啊,int*之类的

1.这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。 如果开辟成功,则返回⼀个指向开辟好空间的指针。
2.如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。

3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。
4.如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

注意第四条,当我们使用的时候,一定要避免这种情况的产生。

当我们使用malloc函数时,我们也可以借助代码去检查他是否错误,错误的原因是什么

#include<stdio.h>
#include<stdlib.h>
int main() {int* a = (int*)malloc(2e9);if (a == NULL) {perror("malloc");return 1;}printf("win");return 0;
}

perror这里就是找到错误得原因2e9就是2乘十的九次字节的空间。

1.2 free

头文件同malloc,#include<stdlib.h>

void free (void* ptr);

free函数⽤来释放动态开辟的内存。
• 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。

• 如果参数 ptr 是NULL指针,则函数什么事都不做。

第一点要注意,你如果ptr指向的内存不是动态开辟的,程序可能会崩掉,所以一定要特别注意

值得一提的是,他这个定义比较抽象,free是什么意思?他说用来释放内存?释放具体是什么意思?很多人其实并没有理解很深刻,所以我来说一段我自己的理解来帮助大家理解

malloc函数的功能是开辟一段空间,但是是开辟吗?,其实理解成赋予比较好一点,例如

int* a = (int*)malloc(40);

他其实是把40大小的空间的使用权限赋予了a,同时将把这40个空间的地址给了a

而free呢?free的作用是相当于把这块内存还给操作系统了,但并没有删除掉a中存储的地址(也就是a仍然指向那块地址,但那块空间已经不属于程序了

那free了之后我们在用a去访问那块地址,就会报错,此时a是野指针,那为了以后产生不必要的错误,我们要把a指针置为空指针

二、calloc和realloc

1.calloc

void* calloc (size_t num, size_t size);

• 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

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

2.realloc

作用:调整动态内存分配的大小。

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

• ptr 是要调整的内存地址
• size 调整之后新⼤⼩
• 返回值为调整之后的内存起始位置。
• 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到 新 的空间。• realloc在调整内存空间的是存在两种情况:
◦ 情况1:原有空间之后有⾜够⼤的空间
◦ 情况2:原有空间之后没有⾜够⼤的空间

情况1
当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。情况2
当是情况2的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。

所以我们要注意,不能写出这样一个代码

 ptr = (int*)realloc(ptr, 1000);

如果申请成功还好,如果失败的话,ptr原有的地址可能找不到了,所以我们应借助另一个指针p,如果成功了,再把p指向的地址赋给ptr。

三.动态内存经典笔试题分析

#include<stdio.h>
#include<stdlib.h>
void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
int main() {Test();return 0;
}

大家先看这道题,输出结果会是什么?

答案出乎意料,为什么呢?注意看,在调用GetMemory函数时,他是直接传的str,str里面存的是NULL,然后在函数调用时,计算机会帮他创建一个临时变量,假设叫p,这个p中拷贝过str里面存的的内容,也是NULL,注意,str和p的地址是不一样的,所以,动态分配给p内存,并没有给str,然后底下又进行对p的控制的内存进行赋值之类的,就会报错,就会崩溃,而且还会发生内存泄漏,因为p的地址你没有释放看,写入位置,正是NULL,0x000....所以我们应该怎么改正呢?我们可以将str的地址传进去

#include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);
}
int main() {Test();return 0;
}

或者把p的地址进行一个返回拿str接受也是可以的。

看下一题

#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main() {Test();return 0;
}

大家不妨先自己做一下,乍一看没有问题,一运行,就g

why?我们来分析一下注意,p是一个临时变量,当函数运行结束的时候,p所支配的空间已经全部还给操作系统了,已经不属于程序了,但是str现在确实指向这片空间,这时候打印,就会出现一堆乱码。

下一题

#include<stdio.h>
#include<stdlib.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main() {Test();return 0;
}

我们会发现欸?居然可以打印,如果仔细阅读前面的我们其实可以发现,free完了之后str已经失去了访问权限,但为什么还能打印呢?注意:此时p已经成为了野指针,访问了不该访问的空间,这里的空间已经成了操作系统的了,但是str仍能访问这片空间,所以打印出来了。

那有的童鞋就问了,那free free了个集贸,什么用都没有,在我看来,free完和free前的区别就是,free完即使你改了,有可能操作系统某个操作给你覆盖了,如果没有free的话,那块空间只能你用。

四.柔性数组

1.表示方法及规则

#include<stdio.h>
#include<stdlib.h>
struct a {int a1;int a2[];
};
struct b {int b1;int b2[0];
};

• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
• sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

第三点如图。

2.使用方法

#include<stdio.h>
#include<stdlib.h>
struct a {int a1;int a2[];
};
struct b {int b1;int b2[0];
};
int main() {struct a* a0 = (struct a*)malloc(sizeof(struct a) + sizeof(int) * 100);return 0;
}

这样柔性数组成员a2,相当于获得了100个整型元素的连续空间。


总结

本篇我们介绍了动态内存分配,malloc以及celloc和relloc还有free,以及几道经典的面试题,如果有帮助还请点个赞哦


http://www.ppmy.cn/embedded/113937.html

相关文章

什么?blender可以云渲染了!

在数字艺术和动画制作的世界里&#xff0c;Blender一直是自由和开源软件的佼佼者。它以其强大的功能和灵活性&#xff0c;赢得了全球艺术家和设计师的青睐。但你知道吗&#xff1f;现在&#xff0c;Blender也可以通过渲染 101云渲染来提高渲染效率了&#xff01; 渲染101云渲染…

Python 数学建模——Fitter 拟合数据样本的分布

文章目录 介绍代码实例 介绍 数学建模中很多时候&#xff0c;我们有某个随机变量 X X X 的若干样本 X 1 , X 2 , ⋯ , X n X_1,X_2,\cdots,X_n X1​,X2​,⋯,Xn​&#xff0c;想要还原随机变量 X X X 的概率密度函数 f ( x ) f(x) f(x)。诚然&#xff0c;高斯核密度估计可以…

solidity-19-fallback

接收ETH receive和fallback receive和callback是solidity中两个特殊的回调函数&#xff0c;一个处理接收ETH,一个处理不存在的函数调用。本质上就是吧fallback拆成了两个回调函数。我暂时不知道什么是fallback fallback调用不存在的函数时会被调用也就是这个函数是不是等价于…

Python基础(八)——MySql数据库

一.数据库 【库——>表——>数据】 借助数据库对数据进行组织存储&#xff0c;借助SQL语言对数据库、数据进行操作管理 Mysql数据库 下载&#xff1a;https://www.mysql.com/ 查看是否安装配置成功&#xff1a; 安装DBeaver用于Mysql数据库图形化 安装&#xff1a;…

MySQL——数据库的高级操作(一)数据备份与还原(1)数据的备份

在操作数据库时&#xff0c;难免会发生一些意外造成数据丢失。例如&#xff0c;突然停电、管理员的操作失误都可能导致数据的丢失。为了确保数据的安全&#xff0c;需要定期对数据库进行备份&#xff0c;这样&#xff0c;当遇到数据库中数据丢失或者出错的情况&#xff0c;就可…

Keil MDK5学习记录

2024.9.19 1. no browse information available in ‘xxx’的问题 成功解决Keil MDK5中no browse information available in ‘xxx’的问题-CSDN博客https://blog.csdn.net/bean_business/article/details/1091894452. .c文件中显示函数列表 如何在Keil5里.c文件中显示函数列表…

LeeCode打卡第二十九天

LeeCode打卡第二十九天 第一题&#xff1a;岛屿数量&#xff08;LeeCode第200题&#xff09;: 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。岛屿总是被水包围&#xff0c;并且每座岛屿只…

Tcp三次握手四次挥手和SSL/TLS

1.Tcp三次握手四次挥手&#xff1a; 1.1基本概念&#xff1a; TCP&#xff08;三次握手和四次挥手&#xff09;是用于建立和终止可靠传输连接的过程。TCP协议是一种面向连接的传输层协议&#xff0c;确保数据在网络上可靠、有序地传输。下面详细解释三次握手和四次挥手的工作机…

浸没边界法精度相关的论文的阅读笔记

Convergence proof of the velocity field for a stokes flow immersed boundary method https://doi.org/10.1002/cpa.20233 研究对象的选取 他这里为什么能够选取一个周期性边界的流场啊&#xff1f;为什么不是狄利克雷边界或者诺伊曼边界&#xff1f; 方形流场的边界值 …

高级算法设计与分析 学习笔记6 B树

B树定义 一个块里面存了1000个数和1001个指针&#xff0c;指针指向的那个块里面的数据大小介于指针旁边的两个数之间 标准定义&#xff1a; B树上的操作 查找B树 创建B树 分割节点 都是选择正中间的那个&#xff0c;以免一直分裂。 插入数字 在插入的路上就会检查节点需不需要…

Testbench编写与Vivado Simulator的基本操作

Testbench编写与Vivado Simulator的基本操作 Testbench编写 Testbench 是一种用Verilog或者systemVerilog语言编写的程序或模块&#xff0c;编写testbench的主要目的是为了对使用硬件描述语言&#xff08;HDL&#xff09;设计的电路UUT(unit under test)进行仿真验证&#xf…

一招教你解决excel表格打印预览时候表格线条显示不全的问题

1、如图&#xff0c;我们在制作好excel表格后再需要打印时候&#xff0c;点击打印预览会出现以下情况&#xff1a; 最下边的表格线条显示不全&#xff0c;这样即使打印出来或者导出为pdf&#xff0c;文件中依然显示不全&#xff0c;这时候我们只需要在excel表格中轻轻设置一下就…

CleanMyMac X 4.15.6正式版 mac直装破解版

你知道 CleanMyMac是什么吗&#xff1f;它的字面意思为“清理我的Mac”&#xff0c;作为软件&#xff0c;那就是一款 Mac清理工具 &#xff0c;Mac OS X 系统下知名系统清理软件&#xff0c;是数以万计的Mac用户的选择。它可以流畅地与系统性能相结合&#xff0c;只需…

JVM 运行时数据区域

目录 前言 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 前言 首先, java程序在被加载在内存中运行的时候, 会把他自己管理的内存划分为若干个不同的数据区域, 就比如你是一个你是一个快递员, 一堆快递过来需要你分拣, 这个时候, 你就需要根据投放的目…

三、(JS)JS中常见的表单事件

一、onfocus、onblur事件 这个很容易理解&#xff0c;就不解释啦。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&…

关于 Goroutines 和并发控制的 Golang 难题

下面是一道关于 Goroutines 和并发控制的 Golang 难题&#xff0c;它涉及到 Go 的并发编程模型、Goroutines、通道&#xff08;Channels&#xff09;以及 sync.WaitGroup 的使用&#xff1a; 问题描述&#xff1a; 你有一个需要并发执行的任务&#xff0c;其中有 100 个 URL …

基于Spring Boot的学生社区故障维修预约系统的设计与实现(开题报告)

毕业论文(设计)开题报告 基于Spring Boot的学生社区故障维修预约系统设计与实现 姓 名 学 院 数学与数据科学学院 专业班级 信息与计算科学202 学 号 202021314223 校内指导教师 职称/职务 副教授 校外指导教师 职称/职务 技术经理 起始时间 2023年9月 教务部制 一、开…

【LLM多模态】文生视频评测基准VBench

note VBench的16个维度自动化评估指标代码实践&#xff08;待完成&#xff09;16个维度的prompt举例人类偏好标注&#xff1a;计算VBench评估结果与人类偏好之间的相关性、用于DPO微调 文章目录 note一、相关背景二、VBench评测基准概述&#xff1a;论文如何解决这个问题&…

Go语言现代web开发13 方法和接口

方法 As you probably noticed, there are no classes in the Go programming language. But we can mimic this by declaring functions on types. The type which declares functions is called the receiver argument and the function declared on the type is called the…

JS基础之【对象详解 -- 对象的属性与方法、遍历对象与内置对象】

&#x1f680; 个人简介&#xff1a;某大型国企高级前端开发工程师&#xff0c;7年研发经验&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码…