C语言_动态内存管理

本章重点

  • 为什么存在动态内存分配

  • 动态内存函数的介绍

    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误

  • 几个经典的笔试题

  • 柔性数组

1. 为什么存在动态内存分配

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

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

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

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

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

2. 动态内存函数的介绍

2.1 malloc 和 free

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

void* malloc (size_t size);

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

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

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

void free (void* ptr);

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

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

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

malloc和free都声明在stdlib.h头文件中。 举个例子:

在这里插入图片描述

输出结果:

在这里插入图片描述

2.2 calloc

C语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数malloc的区别只在于calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。 举个例子:

在这里插入图片描述

输出结果:

在这里插入图片描述

总结:

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

2.3 realloc

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。 函数原型如下:
void* realloc (void* ptr, size_t size);
  • ptr是要调整的内存地址
  • size调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • realloc在调整内存空间的是存在两种情况:
    • 情况1:原有空间之后有足够大的空间
    • 情况2:原有空间之后没有足够大的空间

在这里插入图片描述

以下代码是否正确?

在这里插入图片描述

这样的赋值是错误的,如果扩容失败返回NULL,相当于把p指向为了NULL,不仅扩容失败,连自己开辟的空间也找不到了。正确写法如下:

在这里插入图片描述

3. 常见的动态内存错误

  • 对NULL指针的解引用操作
void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题//所以在开辟动态空间返回地址的时候,我们要先判断返回值是否正确free(p);
}
  • 对动态开辟空间的越界访问
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);
}
  • 对非动态开辟内存使用free释放
void test()
{int a = 10;int *p = &a;free(p);//ok?
}
  • 使用free释放一块动态开辟内存的一部分
void test()
{int *p = (int *)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}
  • 对同一块动态内存多次释放
void test()
{int *p = (int *)malloc(100);free(p);free(p);//重复释放
}
  • 动态开辟内存忘记释放(内存泄漏)
void test()
{int *p = (int *)malloc(100);if(NULL != p){*p = 20;}
}int main()
{test();while(1);
}

动态申请的内存空间,不会因为出了作用域就自动销毁。

忘记释放不再使用的动态开辟的空间会造成内存泄漏。 切记:动态开辟的空间一定要释放,并且正确释放 。

4. 几个经典的笔试题

题目1:

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,然后再函数里面将开辟到的新空间的地址赋值给了p,结束函数。
这里就出现问题了,虽然我们传入的是指针类型的变量,但是也没有什么了不起,想要改变类型就要传入地址然后去解引用访问改变。
结果就是传值调用,并没有对原有的地址进行改变。导致我们strcpy的时候str还是NULL,那么NULL能进行拷贝嘛?那自然而然就会报错。
printf(str)打印是没有问题的,举个例子:printf(“hello world”),本质上是把h的地址给了printf然后往下面连续打印。
注意的是:这样写只能用于字符串形式,单个字符以及整型都不可以进行输出打印。

正确写法:

在这里插入图片描述

题目2:

char *GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}

画图详解

在这里插入图片描述

正确写法:

在这里插入图片描述

题目3:

void GetMemory(char **p, int num)
{*p = (char *)malloc(num);
}void Test(void)
{char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}int main()
{Test();return 0;
}

这里主体并没有过错,只是再最后程序结束的时候并没有释放掉这个动态开辟的空间,所以我们还是需要手动释放一样。

正确写法:

在这里插入图片描述

题目4:

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;
}

这道题错误在于:首先,动态开辟了一块空间,然后拷贝了一串数据进去,再然后释放掉这块空间,此时,str的指向还是这块释放掉的动态空间的起始地址,此时处于野指针状态。又再次对这块进行拷贝,这里就错误了,因为我们已经释放掉了这块空间,这块空间已经不属于我们的了,不能进行拷贝。只需要把这个free去掉,保留这块动态空间即可再次进行拷贝。

5. 柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最
后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

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

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

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

柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应
    柔性数组的预期大小。

例如:

//code1
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

柔性数组的使用

//定义结构体
struct S
{int a;int arr[0];
};int main()
{struct S* s = NULL;//开辟一个动态空间struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);//检查返回地址if (ps == NULL){perror("malloc:");return 0;}s = ps;s->a = 100;int i = 0;for (i = 0; i < 10; i++){s->arr[i] = i + 1;}//打印printf("%d\n", s->a);for (i = 0; i < 10; i++){printf("%d ", s->arr[i]);}printf("\n");//增容struct S* ptr = realloc(s, sizeof(struct S) + 60);if (ptr == NULL){perror("realloc:");return 0;}s = ptr;s->a = 15;for (i = 0; i < 15; i++){s->arr[i] = 15 - i;}//打印printf("%d\n", s->a);for (i = 0; i < 15; i++){printf("%d ", s->arr[i]);}printf("\n");free(s);s = NULL;return 0;
}

输出结果:

在这里插入图片描述

本章完~

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

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

相关文章

Maven进阶——坐标、依赖、仓库

目录 1.pomxml文件 2. 坐标 2.1 坐标的概念 2.2 坐标的意义 2.3 坐标的含义 2.4 自己项目的坐标 2.5 第三方项目坐标 3. 依赖 3.1 依赖的意义 3.2 依赖的使用 3.3 第三方依赖的查找方法 3.4 依赖范围 3.5 依赖传递和可选依赖 3.5.1 依赖传递 3.5.2 依赖范围对传…

算法的学习笔记—数组中的逆序对(牛客JZ51)

&#x1f600;前言 在算法和数据结构领域&#xff0c;"逆序对"是一个经典问题。它在数组中两个数字之间定义&#xff0c;若前面的数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。我们要做的就是&#xff0c;给定一个数组&#xff0c;找出数组中所有的逆…

Docker 镜像下载问题及解决办法

Docker 镜像下载问题及解决办法 我在杂乱的、破旧的村庄寂寞地走过漫长的雨季&#xff0c;将我年少的眼光从晦暗的日子里打捞出来的是一棵棵开花的树&#xff0c;它们以一串串卓然不俗的花擦明了我的眼睛&#xff0c;也洗净了我的灵魂。 引言 在使用 Docker 时&#xff0c;用户…

【AI绘画】Midjourney进阶:对角线构图详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;什么是构图为什么Midjourney要使用构图 &#x1f4af;对角线构图特点应用场景提示词书写技巧测试 &#x1f4af;小结 &#x1f4af;前言 【AI绘画】Midjourney进阶&a…

免费送源码:Java+MVC+HTML+CSS +MySQL 考研资料共享系统的设计与实现 计算机毕业设计原创定制

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设考研资料共享系统。 本…

Win10+MinGW13.1.0编译Qt5.15.15

安装windows SDK、python、ruby、cmake、Perl[可选]安装MySQL解压qt-everywhere-opensource-src-5.15.15.zip&#xff08;注&#xff1a;不要使用qt-everywhere-opensource-src-5.15.15.tar.xz&#xff09;修改源代码 E:\qt-everywhere-src-5.15.15\qtbase\src\3rdparty\angle\…

028_Comma_Separated_List_in_Matlab中的逗号分割列表

什么是逗号分割列表 这玩意一般都不知道是什么&#xff0c;Comma-separated list&#xff0c;CSL&#xff0c; 虽然&#xff0c;用Matlab的时候天天会用到。这到底是个什么玩意&#xff1f;或者&#xff0c;更进一步&#xff0c;这到底是不是个玩意&#xff1f; 每次调用一个…

CSS3 动画相关属性实例大全(三)(columns、filter、flex、flex-basis 、flex-grow、flex-shrink属性)

CSS3 动画相关属性实例大全&#xff08;三) &#xff08;columns、filter、flex、flex-basis 、flex-grow、flex-shrink属性&#xff09; 本文目录&#xff1a; 一、columns属性&#xff08;设置元素的列宽和列数&#xff09; 二、filter属性&#xff08;调整图像、背景和边…

网络一些相关术语

目录 网络一些相关术语 转发平面效率 可扩展性 控制平面 网络拓扑 服务质量&#xff08;QoS&#xff09; 网络协议 网络带宽 网络拥塞 网络安全 网络冗余 网络切片 网络延迟 网络地址转换&#xff08;NAT&#xff09; 虚拟专用网络&#xff08;VPN&#xff09; …

网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?

文章整体介绍 本文旨在解答关于微服务网关的三个核心问题&#xff1a; 1&#xff09;为什么需要网关&#xff1f;也即在何种场景下应采用微服务网关以优化系统架构&#xff1b; 2&#xff09;什么是微服务网关&#xff1f;主要讲构成微服务网关的关键能力&#xff0c;包括但…

008:光盘映像文件处理工具UltraISO安装教程

摘要&#xff1a;本文详细介绍光盘映像文件处理工具UltraISO的安装流程。 一、软件介绍 UltraISO是一款功能强大的光盘映像文件处理工具&#xff0c;支持ISO文件的制作、编辑、转换、压缩、刻录以及启动盘制作&#xff0c;广泛应用于数据备份、软件分发和系统安装等领域。 二…

从GPT定制到Turbo升级再到Assistants API,未来AI世界,你准备好了吗?

引言 在OpenAI DevDay发布会上&#xff0c;OpenAI再次震撼整个人工智能行业&#xff0c;为AI领域带来了重大的更新。CEO Sam Altman宣布推出了定制版本的ChatGPT&#xff0c;这意味着用户现在可以根据自己的需求打造个性化的GPT&#xff0c;并分享至GPT Store。这一消息对于受…

神经架构搜索:自动化设计神经网络的方法

在人工智能&#xff08;AI&#xff09;和深度学习&#xff08;Deep Learning&#xff09;快速发展的背景下&#xff0c;神经网络架构的设计已成为一个日益复杂而关键的任务。传统上&#xff0c;研究人员和工程师需要通过经验和反复试验来手动设计神经网络&#xff0c;耗费大量时…

【MySQL】日志

1. 日志基本了解 常见的MySQL Server日志类型&#xff0c;以及记录的日志信息&#xff08;场景通俗理解&#xff09; 错误日志 记录的主要信息由服务器关闭、启动、崩溃事件&#xff1b;MySQL运行过程中出现的错误、警告和严重事件以及与权限、配置相关的问题使用场景 诊断MyS…

【Linux】【xmake】安装 + C/C++常用项目配置

文章目录 0. 环境准备1. 子命令create - 快速创建项目build - 构建程序config - 配置编译需要的参数show - 查看当前工程基本信息update - 程序自更新 2. C/C 项目常用配置2.1 项目目标类型2.2 添加宏定义2.3 头文件路径和链接库配置2.4 设置语言标准2.5 设置编译优化2.6 添加源…

光伏MPPT追踪的仿真设计

利用Simulink可实现如下功能&#xff1a;改变光照时有MPPT追踪并低电压穿越的能力。 MPPT控制器的全称为“最大功率点跟踪”&#xff08;Maximum Power Point Tracking&#xff09;太阳能控制器&#xff0c;检测主回路直流电压及输出电流&#xff0c;计算出太阳能阵列的输出功…

5.15 加载内核映像文件(1)

首先是 连接脚本与 实际的内核映像大小的关系&#xff1a; 关于ELF 格式的了解&#xff1a; 如何通过 ELF 头&#xff0c; 找到各个段。 网上的关于elf 的截图&#xff1a; 那么 segment 与 section 有什么区别呢&#xff1f; 也就是说&#xff0c; section值得是 单个C文件的…

021、深入解析前端请求拦截器

目录 深入解析前端请求拦截器&#xff1a; 1. 引言 2. 核心实现与基础概念 2.1 基础拦截器实现 2.2 响应拦截器配置 3. 实际应用场景 3.1 完整的用户认证系统 3.2 文件上传系统 3.3 API请求缓存系统 3.4 请求重试机制 3.5 国际化处理 4. 性能优化实践 4.1 请求合并…

VisionPro - 高级 - 保存模式以备后用 - 中心圆的查找配置

前言: 在基础篇, VisionPro Basic - 01- 有关应用和作业-CSDN博客 我们提到了应用和作业的保存,那么这些都是vpp的保存格式。 我们知道,在模式工具的配置中,如果我们做好了很多的调试,最后配置好参数后,也有一个保存模式的选项。我们在保存的时候,一定要添加前缀或…

GIT使用list

清空当前commit区 方法 1&#xff1a;软重置到初始状态 如果希望保留文件内容&#xff0c;但清空所有 commit 历史&#xff0c;可以使用以下命令&#xff1a; git reset --soft $(git rev-list --max-parents0 HEAD)解释&#xff1a; --soft 表示重置 commit 历史&#xff…