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

【C语言】动态内存开辟

写在前面

C语言中有不少开辟空间的办法,但是在堆上开辟的方法也就只有动态内存开辟,其访问特性与数组相似,但最大区别是数组是开辟在栈上,而动态内存开辟是开辟在堆上的。这篇笔记就让不才娓娓道来。
PS:本篇没有目录实在抱歉CSDN对MK语法支持太差了~🫥😶


一、为什么存在动态内存分配

我们在C语言中已经掌握的内存开辟方式有:

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

但是上述的开辟空间的方式有几个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
  3. 都是在栈内存中开辟的空间。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 而且我们知道栈内存空间是相对较小的,如果我们需要存放大空间的数据是,一直在栈空间中不释放,很容易造成栈溢出(stack overflow)。堆内存是可以给我们程序猿自己开辟空间的内存,堆内存相比栈是大了非常非常多,一般堆空间和我们硬件的内存相关如(1t,512GB等)。 所以这时候就只能试试动态存开辟了。


二、动态内存函数的介绍

需要调用<stdlib.h>头文件

2.1、 malloc和free

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

void* malloc (size_t size);

  • size_t size:代表需要开辟多少字节空间
  • malloc是在堆内存上开辟一个大小为size个字节连续空间
  • malloc开辟的内存空间不会进行初始化操作。

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器。

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

void free (void* ptr);

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

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。
  • free释放指针必须指向动态内存的起始地址

malloc与free一般是成对出现的,在程序中,只要不再使用malloc开辟的空间就一定要free,如果不free会造成内存泄漏。如果在大项目中造成内存泄漏,可能会导致灾难。

#include <stdio.h>
int main()
{//代码1int num = 0;scanf("%d", &num);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;
}
  • malloc开辟了 num乘4个字节。
  • 因为是在堆上开辟的连续内存空间,所以可以使用指针+1的方式来范围对应的类型空间
  • 因为ptr是int*类型,所以需要把malloc放回的void*类型强制类型转换成 int*
  • 在free掉动态内存开辟的空间后,prt就变为了野指针,此时需要把prt重新赋值为NULL,以确保不会造成野指针访问。

2.2、calloc

C语言还提供了一个动态内存开辟可以初始化内存的函数叫calloc ,calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

  • 与函数malloc 的区别只在于calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
  • 函数的功能是为num 个大小为size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 虽然calloc可以初始化内存,但是对比起malloc的效率就相对较差,需要灵活运用calloc和malloc来动态开辟内存。
    在这里插入图片描述
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){//使用空间}free(p);p = NULL;return 0;
}

结果为:
在这里插入图片描述


2.3、realloc

在前面的malloccalloc都是固定开辟size个字节内存,如果存满了就需要使用realloc函数来进行扩容。
函数原型:

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

  • ptr 是要调整的malloccalloc的内存地址
  • size 调整之后新大小,size定义多大realloc就会创建多大,一般使用n倍来防止扩容扩小。
  • 在C语言标准中,没有规定realloc不能缩小空间,但是在日常使用中,不推荐使用realloc函数缩容,因为在大工程里面,不是所有人都知道此前的动态内存回缩小,从而导致BUG。
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
    realloc在调整内存空间的是存在两种情况:
    • 情况1:原有空间之后有足够大的空间:要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。(如下图)在这里插入图片描述

    • 情况2:原有空间之后没有足够大的空间:在堆空间上另找一个合适大小的连续空间来使用。若开辟成功:函数返回的是一个新的内存地址。 而且realloc函数会把原动态开辟的地址自动free掉(如下图)。若开辟失败函数将返回空不改变原空间
      在这里插入图片描述在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = malloc(100);if (ptr != NULL){//业务处理}else{perror("malloc::");}//扩展容量int* p = realloc(ptr, 1000);if (p != NULL){printf("%p\n", p);printf("%p\n", ptr);ptr = p;p = NULL;}free(ptr);return 0;
}

结果为:
在这里插入图片描述
上图中我们可以看到,如果扩容空间太大,遇到情况2时候,我们打印原地址和扩容后的地址发现是不相同的。


三、常见的动态内存错误

3.1、对NULL指针的解引用操作、对非动态开辟内存使用free释放

void test()
{int* p = NULL;free(p);//如果p的值是NULL,就会有问题,指针不需要free
}

3.2、对动态开辟空间的越界访问

与数组越界访问相似。

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

3.3、使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}
  • free释放指针必须指向动态内存的起始地址

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

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

3.5、动态开辟内存忘记释放(内存泄漏)

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

忘记释放不再使用的动态开辟的空间会造成内存泄漏。


四、了解C/C++程序的内存开辟

C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

为了更直观理解,我们对应下图一起理解:
在这里插入图片描述


五、柔性数组

柔性数组其本质还是使用动态内存开辟空间,只不过是使用数组的形式进行接收罢了。
对于不才来说对于用指针来接收,唯一的优势只有:

  • 如果我们的代码是在一个给别人用的函数中,可以把把结构体的内存以及其成员需要的内存通过柔性数组的特性一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。这样会降低用户忘记释放内存导致内存泄漏。

5.1柔性数组的定义

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

例如:

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

有些编译器会报错无法编译可以改成:

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

**柔性数组的特点: **

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof操作符 返回的这种结构大小 不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
    在这里插入图片描述
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;int main() {printf("%d\n", sizeof(type_a));int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));//业务处理p->i = 5;for (i = 0; i < 5; i++){p->a[i] = i;}printf("%d\n", sizeof(type_a));free(p);return 0;
}

结果为:
在这里插入图片描述

  • 上图中我们明显看出,结构体的成员变量i也是存放在柔性数组中的。
  • 无论柔性数组有无动态开辟内存sizeof操作符的计算都不包括柔性数组的内存。

当然,上面的代码可以定义为一个整形指针来接收,也是可以实现上述代码的功能
在这里插入图片描述

typedef struct st_type
{int i;int* p_a;
}type_a;int main() {int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a));assert(p);p->i = 5;p->p_a = (int*)malloc(p->i * sizeof(int));assert(p->p_a);//业务处理for (i = 0; i < 5; i++){p->p_a[i] = i;}//释放空间free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

结果为:
在这里插入图片描述
上述创建指针也是可以完成同样的功能,但是使用柔性数组的实现有两个好处:

  • 第一个好处是:方便内存释放(也是不才觉得唯一的优势…🥶🫡🫡)
    • 如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。
    • 用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员需要free,所以你不能指望用户来发现这个事。
    • 所以,如果我们把结构体的内存以及其成员需要的内存通过柔性数组的特性一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉
  • 第二个好处是:这样有利于访问速度(其实,我个人觉得也没多高了,反正你跑不了
    要用做偏移量的加法来寻址,时间复杂度还是O(n) )
    • 连续的内存有益于提高访问速度,也有益于减少内存碎片。

对于在结构体中的指针还是柔性数组这两个区别,可以阅读一下 陈皓大神写的C语言结构体里的成员数组和指针文章


以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 若有帮助不要吝啬点赞哟~~💖💖

ps:表情包来自网络,侵删🌹

相关文章:

【C语言】动态内存开辟

写在前面 C语言中有不少开辟空间的办法&#xff0c;但是在堆上开辟的方法也就只有动态内存开辟&#xff0c;其访问特性与数组相似&#xff0c;但最大区别是数组是开辟在栈上&#xff0c;而动态内存开辟是开辟在堆上的。这篇笔记就让不才娓娓道来。 PS:本篇没有目录实在抱歉CSD…...

Redis缓存在thinkPHP/fastAdmin框架中的应用

Redis缓存在thinkPHP/fastAdmin框架中的应用 引言 在现代Web开发中&#xff0c;性能优化是一个永恒的话题。随着用户数量的增加和业务逻辑的复杂化&#xff0c;如何提高应用的响应速度和处理能力成为了开发者们必须面对的挑战。缓存技术作为提升性能的有效手段之一&#xff0…...

Ceisum无人机巡检视频投放

公司投标内容有个视频投放的功能动画&#xff0c;原本想实现这么一个效果&#xff1a; 案例效果来自别人的展示作品&#xff0c;Leader一眼就相中了这个效果&#xff0c;可惜别人的终究是别人的&#xff0c;又不会白白给你&#xff0c;终究是要自己动手尝试。 动画方面的展示…...

分享几款开源好用的图片在线编辑,适合做快速应用嵌入

图片生成器是指一种工具或软件&#xff0c;用于自动生成图片或图像内容&#xff0c;通常依据用户设定的参数或模板进行操作。这种工具能够帮助用户快速创建视觉效果丰富的图像&#xff0c;而无需具备专业的设计技能。 在数字化时代&#xff0c;图片编辑已经成为日常工作和生活的…...

闪存学习_1:Flash-Aware Computing from Jihong Kim

闪存学习_1&#xff1a;Flash-Aware Computing from Jihong Kim 前言一、Storage Media&#xff1a;NAND Flash Memory1、概念2、编程和擦除操作3、读操作4、异地更新操作&#xff08;Out-Place Update&#xff09;5、数据可靠性6、闪存控制器&#xff08;SSD主控&#xff09;7…...

如何将本地项目上传至Gitee仓库(详细教程)

前提条件 1、本地电脑安装Git客户端 2、本地已有项目 3、Gitee注册好了账户 如果没有安装Gitee 可以区菜鸟查看一下安装教程 Git教程https://www.runoob.com/git/git-tutorial.html 操作示例 前提条件已经准备好的情况下登录gitee 码云 https://gitee.com 点解右侧加号 新…...

redis详细教程(3.hash和set类型)

hash Redis中的Hash是一种数据结构&#xff0c;用于存储键值对集合。在Redis中&#xff0c;Hash非常适合表示对象&#xff0c;其中对象的每个字段都对应一个键值对。以下是关于Redis中Hash的详细讲解&#xff1a; 特点&#xff1a; 1. 键值对集合&#xff1a;Hash是一个包含…...

性能测试详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、 性能测试术语解释 1. 响应时间 响应时间即从应用系统发出请求开始&#xff0c;到客户端接收到最后一个字节数据为止所消耗的时间。响应时间按软件的特点…...

修改ruoyi的logo和标题

1、修改网页标签上的图标和标题 静态修改 标签页上的 图标 直接替换public/favicon.ico文件为你自己的图标文件 如果你的图标文件名不是favicon.ico&#xff0c;你还需要在public/index.html中相应地修改这一行&#xff0c;将/favicon.ico替换为你的图标文件路径。 静态修改…...

vue系列==vue3新语法

vue系列vue3新语法 1、setup组合式API入口函数 2、利用ref函数定义响应式数据 3、利用reactive函数定义响应式数据 1、setup组合式API入口函数 1、Vue3为组合式API提供了一个setup函数&#xff0c;所有组合式API函数都是在此函数中调用的&#xff0c;它是组合式API的使用入口…...

001-Kotlin界面开发之Jetpack Compose Desktop学习路径

Compose Desktop学习之路 学习过程 理解Kotlin的基本语法 Compose Desktop采用Kotlin构建&#xff0c;因此对Kotlin的基本语法有很好的理解是必不可少的。你可以从官方的Kotlin文档开始。 用一句话概括&#xff0c;Kotlin是一种现代的、静态类型的编程语言&#xff0c;它结合…...

qt QStackedLayout详解

QStackedLayout类提供了一种布局方式&#xff0c;使得在同一时间内只有一个子部件&#xff08;或称为页面&#xff09;是可见的。这些子部件被维护在一个堆栈中&#xff0c;用户可以通过切换来显示不同的子部件&#xff0c;适合用在需要动态显示不同界面的场景&#xff0c;如向…...

python实现钉钉群机器人消息通知(消息卡片)

直接上代码 python """ 飞书群机器人发送通知 """ import time import urllib3 import datetimeurllib3.disable_warnings()class DingTalkRobotAlert():def __init__(self):self.webhook webhook_urlself.headers {Content-Type: applicatio…...

防火墙技术应用

目录 防火墙安全功能指标防火墙性能指标防火墙部署防火墙应用案例-Linux防火墙防火墙应用案例-华为包过滤防火墙ACL(基本)防火墙应用案例-工控防火墙部署 防火墙安全功能指标 我们防火墙也会提供各种各样的互联接口&#xff0c;它这种接口有以太网、快速以太网、千兆以太网&am…...

docker engine stopped

1&#xff09;环境&#xff1a;win 10 2&#xff09;docker安装时已经已经安装了虚拟机 3&#xff09;启用网络适配器 4&#xff09;启用docker服务&#xff08;依赖服务LanmanServer&#xff09; 5&#xff09;全都弄好了&#xff0c;docker还是打不开&#xff0c;没办法了&a…...

Redis- 内核的分配内存限制的警告“WARNING Memory overcommit must be enabled!”

文章目录 问题描述报错原因影响解决方案注意事项 问题描述 [rootredisxxx]# redis-sentinel ./sentinel.conf 19638:X 01 Nov 2024 16:57:27.180 # WARNING Memory overcommit must be enabled! Without it, background save or replication may fail under low memory condi…...

腾讯云在线扩容数据盘

文章目录 一、查询云硬盘使用的分区形式二、根据不同的云硬盘类型进行在线扩容分区2.1 安装 gdisk 工具。2.2 安装 growpart 工具使用 growpart 工具扩容分区 三、根据不同的云硬盘类型进行在线扩容文件系统3.1 扩容 EXT 文件系统3.2 扩容 XFS 文件系统 一、查询云硬盘使用的分…...

Time Travel Queries|在 RisingWave 中访问历史数据

对于许多组织来说&#xff0c;能够访问历史数据十分关键。以金融交易公司为例&#xff1a; 特定时间点的数据快照与合规审查、审计流程息息相关。企业的关键业务系统也依赖历史数据来恢复故障并重建系统。分析过去事件及其对当前状况的影响&#xff0c;还有助于准确预测并制定…...

Unity之UnityWebRequest

复习UnityWebRequest 介绍下载文件和上传文件下载进度和异常验证文件DownloadHandlerScript 介绍 unity中使用UnityWebRequest来代替所有HTTP请求提供了三种API,UnityWebRequest.Get&#xff0c;UnityWebRequest.Post&#xff0c;UnityWebRequest.Put分别处理GET,POST,PUT请求…...

使用 pkg 打包 Puppeteer 应用:跨平台自动化的轻量级选择

使用 pkg 打包 Puppeteer 应用 pkg 是一个非常强大的工具&#xff0c;它允许你将 Node.js 项目打包成独立的可执行文件&#xff0c;这意味着你可以在没有安装 Node.js 环境的设备上运行该应用。这对于希望分发不暴露源代码的应用程序、创建跨平台的快速演示版本或简化部署流程…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...