动态内存管理,malloc和calloc以及realloc函数用法

目录

一.malloc函数的介绍

malloc的用法

举个例子

注意点 

浅谈数据结构里的动态分配空间

二.calloc函数的介绍

三.realloc函数的介绍

四.柔性数组的介绍

为什么有些时候动态内存函数头文件是malloc.h,有些时候却是stdlib.h


一.malloc函数的介绍

malloc其实就是动态开辟空间,其实就是让程序员自己去决定开辟多大空间 

int  a=5,在栈空间上开辟4个字节去存变量a,char b=1在栈空间上开辟1个字节去存变量b,包括数组char arr[10]以上空间都是固定的,这种开辟空间是编译器自动完成分配的,在程序运行之前就已经分配好了,编译器负责将源代码翻译成机器语言,并确定变量在内存中的存储方式和位置。操作系统负责管理内存并为程序分配空间,但编译器决定了变量在内存中的布局和大小。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
道,那数组的编译时开辟空间的⽅式就不能满⾜了。
C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

malloc的用法

void * malloc (size_t size)

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
• 如果开辟成功,则返回⼀个指向开辟好空间的指针。
• 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
• 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃
⼰来决定。
• 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

举个例子

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{int arr[10] = { 5,4,6,3,4,5,7,8,9,10 };int* ptr = (int*)malloc(20 * sizeof(int));if (ptr != NULL) {memcpy(ptr, arr, 10 * sizeof(int)); // 将原数组的内容拷贝到新分配的内存中for (int i = 0; i < 10; i++){printf("%d ", *(ptr + i));}}free(ptr);//结束要把指针指向的空间释放,将指针置为空ptr = NULL;
}

 

(int*)malloc(20 * sizeof(int)),动态分配20个int类型的空间,最后返回值是指向这片空间的指针,我这片空间是准备用来存整型的(int),所以要强制转换成(int *),用整型指针int *ptr来接收

同样也可以写成这样(int *)malloc(80),给一个具体的要开辟空间的大小数字

动态内存题目

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

本意是在函数Get中给str分配空间,然后把字符串“hello world”复制给它

结果是直接报错,运行不起来,为什么呢。

str是个指针,本来应该放置的是它指向空间的地址,但是此时置为空指针,所以传过去用指针形式的p去接收。但是接收到的是str里面的值,而不是str指向地址,此时p里面的值也为NULL。

 

再然后开辟了一个一百字节大小的空间,并把这个地址赋值给了p

再然后问题就来了,此时str其实是传值传参,形参是实参的一份临时拷贝,形参的改变不会影响实参,str不会去指向开辟的那个空间的地址,str里面依旧为NULL,p出了函数这个作用域就会销毁了,它只能在作用域内使用

所以第一个改进方案来了,形参不是只能在它这个作用域里有效吗,那么我直接在作用域里完成strcpy操作不就行了,p是实打实保存的是指向我们开辟空间的地址,所以在p相关的函数里完成剩下的操作就行

第二种解决方案,str最后因为函数作用域以及传值传参的影响,p的值改变不会影响实参str,但是return过去的值我可以用str接收到啊,直接把p此时的值(此时p内部是保存开辟的100空间的地址)return回去,让str接收依旧能完成接下来的操作,相当于现在str内部保存的不是NULL而是开辟100字节的空间的地址了

第三种解决方案,既然传值传参是临时拷贝,形参的改变不会影响实参,那么 有没有什么办法改变实参呢。传地址传参过去,直接解引用操作不就是在原来的地方操作吗,但是需要注意的是此时str是个指针变量,它取地址传过去要用二级指针接收

 别忘了要手动用free释放开辟的空间,同时把指针置为空

注意点 

1.使用malloc分配空间一定要先判断接收的指针是否为空,然后再去使用。这是因为malloc函数分配空间不是百分百能成功的,如果分配空间太大,而系统原有的空间不足,这时malloc函数将返回NULL。所以为了确保程序的健壮性,我们通常会在使用malloc函数分配内存空间后,先检查返回的指针是否为NULL

2.在使用完了malloc函数末尾一定要将这片空间释放掉,malloc函数分配的空间不会自动释放,它将一直占用系统资源,直到程序结束才会被系统回收。忘记释放不再使用的动态开辟的空间会造成内存泄漏

3.free释放完空间后,一定要将接收的指针置为空,否则的话该指针会变成野指针。如果我们在使用已经释放的内存空间的指针,可能会导致程序崩溃或者产生不可预期的结果。

浅谈数据结构里的动态分配空间

#include<stdio.h>
typedef char ElemType;
#define MaxSize 50
#include<stdlib.h>
typedef struct
{ElemType data[MaxSize];//存放线性表中的元素int length;//存放线性表的长度
}SqList;//顺序表的类型
void CreateList(SqList*& L, ElemType a[], int n)
{int i = 0, k = 0;L = (SqList*)malloc(sizeof(SqList));while (i < n){L->data[k] = a[i];k++;i++;}L->length = k;
}

以上常见的顺序表的声明和创建顺序表的函数

typedef struct
{
    ElemType data[MaxSize];//存放线性表中的元素
    int length;//存放线性表的长度
}SqList;//顺序表的类型

正常来讲,结构体末尾应该是结构体变量,但是用了typedef将结构体起个小名为了SqList,所以SqList是个类型名,意思是这个顺序表类型名叫SqList

typedef char ElemType;也是这个意思,把char类型取个小名ElemType,但是注意小名能用,大名同样也能用,都是指同一个东西

void CreateList(SqList*& L, ElemType a[], int n)    这个SqList *L其实是结构体指针,为什么会写得这么奇怪呢SqList*& L,这个&不是取地址的意思,而是引用的意思。c语言里传值传参形参的改变不会影响实参,通过引用可以在函数内部修改指针L所指向的结构体,而不是仅仅修改指针本身的值。但是需要注意的是c语言没有引用,引用是c++里面的,所以有些纯c语言编译器可能会报错。

 同样纯c语言编译器中,可以通过用二级指针SqList **L接收L这个指针变量的地址,这时也可以在函数内部修改指针L所指向的结构体

 (SqList*)malloc(sizeof(SqList));此时是结构体类型,所以要强制转换成结构体指针类型,然后才能用结构体指针L去接收。L=(SqList *)malloc(sizeof(SqList))意思就是分配一个空间去存顺序表,空间大小为结构体大小

二.calloc函数的介绍

这个函数其实和malloc函数差不多,都是动态分配空间,区别在于malloc开辟了空间,这个空间里的值它不会给你初始化,里面的值一开始都是随机值。而calloc它给你开辟空间的同时,还会帮你初始化为0

可以很明显的看出,malloc函数开辟的空间除了我放的五个值,其他的都是随机值 

calloc函数你不指定值,它默认初始化为0

 

malloc函数参数只有一个 ,纯数字也可以,例如(强制转换的类型)malloc(100)

而calloc函数参数有两个,(强制转换的类型)calloc(要开辟多少个这样尺寸的元素,要转换类型的尺寸)。   例如(int *)calloc(10,sizeof(int))意思就是开辟10个int类型的空间

三.realloc函数的介绍

有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时
候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤
⼩的调整

说白了,realloc就是对用malloc函数和calloc函数开辟空间大小的修改

void* realloc (void* ptr, size_t size)

ptr 是要调整的内存地址
• size 调整之后新⼤⼩
• 返回值为调整之后的内存起始地址,用指针接收
• 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。
 

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc( 10*sizeof(int));//开辟一个可以存10个int类型的空间if (p != NULL){for (int i = 0; i < 5; i++)//只放五个值来观察一下剩下的初始值{*(p + i) = i;}for (int i = 0; i < 10; i++)//现在看看所有的值的情况{printf("%d ", *(p + i));}}int* pre = (int*)realloc(p, 20 * sizeof(int));if (pre != NULL){p = pre;}else{printf("出错了");}
}

以上代码就是malloc函数已经分配了10个int类型空间基础上再用realloc函数扩容至20个整型的情况

realloc扩容有两种情况,在malloc函数扩容空间的后面再加上需要扩容的空间,如果后面空间足够没有被别的数据占据的话,会直接在后面增加需要扩容的空间,同时返回的是之前malloc函数开辟空间的地址。

第二种情况,如果后面扩容的空间不够,已经被别的数据用了,那么realloc函数会在堆区另外找一个合适大小的空间去使用。会把原先malloc开辟空间的数据直接拷贝过来,然后释放删除原先的那块空间,返回的是在堆区新找空间的地址

需要注意的是虽然是扩容,但是realloc函数也可能扩容失败返回NULL ,如果用记住了malloc地址的p指针直接去接收realloc扩容后空间的地址,此时扩容失败返回NULL,会直接失去原先malloc函数中已经保存的数据。所以要先用pre判断是否为空,再然后把记住新空间地址的指针变量pre赋值给p

四.柔性数组的介绍

柔性数组是指结构中最后一个元素允许是未知大小的数组,柔性数组前面必须至少一个其它成员。具体表现形式如下

struct stu
{int i;int arr[];//也可以int arr[0];
};

一开始sizeof这个结构体是不会把这个数组加进去计算内存对齐的,结构体的大小只是柔性数组前面成员的大小

只有int类型成员的大小 

在使用这个数组之前要采用malloc函数给这个数组开辟至少大于结构体大小的空间

开辟方法可以选择malloc函数开辟结构体字节大小的基础上在加上需要给柔性数组开辟多大的空间的值,这样就保证了分配给柔性数组的大小大于结构体的大小,适应柔性数组预期大小

#include<stdio.h>
#include<stdlib.h>
struct stu
{int i;int arr[];//也可以int arr[0];
};
int main()
{struct stu* pre = (struct stu*)malloc(sizeof(stu) + 100 * sizeof(int));pre->i = 200;//给int i赋初始值200if (pre != NULL){for (int j = 0; j < 100; j++){pre->arr[j] = j;//给柔性数组赋值,检验一下,是否开辟空间成功printf("%d ", pre->arr[j]);}free(pre);pre = NULL;}
}

此时再来看一下这个结构体的大小

 

为什么依旧是4呢,这是因为malloc函数是在程序运行时动态分配的 ,而sizeof计算大小是在编译时计算的,所以无法包括柔性数组的大小。要计算柔性数组的大小,需要单独进行计算或者使用其他方法来获取其大小。意思计算sizeof先计算了结构体的大小,然后柔性数组才被分配了空间大小

第二种开辟方法,先单独给结构体开辟空间,然后再给柔性数组开辟空间

 第⼀个方法好处是⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。用户调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个方法的好处是:这样有利于访问速度连续的内存有益于提⾼访问速度,也有益于减少内存碎片

为什么有些时候动态内存函数头文件是malloc.h,有些时候却是stdlib.h

据C语言标准,malloc.h不是标准C头文件,而是在早期的一些C编译器中使用的头文件。而stdlib.h是标准C头文件,包含了malloc函数的声明以及其他一些常用函数的声明。

在现代的C编译器中,通常推荐使用stdlib.h而不是malloc.h,因为stdlib.h是标准C头文件,可以在所有符合C标准的编译器中使用,而malloc.h可能不是所有编译器都支持。

因此,尽管在某些旧的代码中可能会看到使用malloc.h的情况,但在新的开发中,应该优先选择使用stdlib.h来包含malloc函数的声明。

虽然malloc.h名字没有包含realloc和calloc,但是其实这两个函数也能用这个头文件。

数据结构课本上有些时候还是使用malloc.h头文件来使用动态内存函数

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

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

相关文章

linux内核使用ppm图片开机

什么是ppm图片 PPM&#xff08;Portable Pixmap&#xff09;是一种用于存储图像的文件格式。PPM图像文件以二进制或ASCII文本形式存储&#xff0c;并且是一种简单的、可移植的图像格式。PPM格式最初由Jef Poskanzer于1986年创建&#xff0c;并经过了多次扩展和修改。 PPM图像…

Appium 图像识别技术 OpenCV

在我们做App自动化测试的时候&#xff0c;会发现很多场景下元素没有id、content-desc、text等等属性&#xff0c;并且有可能也会碰到由于开发采用的是自定义View&#xff0c;View中的元素也无法识别到&#xff0c;很多的自动化测试框架对此类场景束手无策。Appium在V1.9.0中有给…

【AI美图】第02期效果图,AI人工智能全自动绘画,美图欣赏

今天给大家献上一组最新提示词 参照图生成图像 依据参照图生成新的图像需要掌握一些技巧&#xff0c;以下是一些可能有用的技巧&#xff1a; 观察参照图&#xff1a;在开始生成新图像之前&#xff0c;仔细观察参照图是非常重要的。你需要了解图像的布局、颜色、线条、细节等…

基于SSM的图书馆预约座位系统的设计与实现(部署+源码+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于SSM的图书馆预约座位…

十六、YARN和MapReduce配置

1、部署前提 &#xff08;1&#xff09;配置前提 已经配置好Hadoop集群。 配置内容&#xff1a; &#xff08;2&#xff09;部署说明 &#xff08;3&#xff09;集群规划 2、修改配置文件 MapReduce &#xff08;1&#xff09;修改mapred-env.sh配置文件 export JAVA_HOM…

初识JVM底层知识,一文读懂JVM知识文集。

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

高并发如何实现单用户信息查询接口

高并发如何实现单用户信息查询接口 故事情节 产品&#xff1a;小李&#xff0c;有个单用户信息查询的功能&#xff0c;需要你实现一下小李&#xff1a;这还不简单&#xff0c;两分钟我给你实现两分钟过去…小李&#xff1a;欧克了&#xff0c;部署上线了运维&#xff1a;哪个…

【1】自动化测试环境配置(ARM服务器)

想要从事 or 了解自动化测试开发、装备开发的小伙伴&#xff0c;本专栏内容将从0到1学习如何针对ARM服务器产品进行自动化测试平台的搭建&#xff0c;包括&#xff1a;测试界面的实现&#xff08;GUI&#xff09;、测试项的功能实现&#xff08;压力测试、接口测试、版本更新&a…

Git使用rebase和merge区别

Git使用rebase和merge区别 模拟环境使用merge合并使用rebase 模拟环境 本地dev分支中DevTest增加addRole() 远程dev被同事提交增加了createResource() 使用merge合并 使用idea中merge解决冲突后, 推送远程dev后,日志图显示 使用rebase idea中使用功能rebase 解决冲突…

机器学习算法---回归

1. 线性回归&#xff08;Linear Regression&#xff09; 原理&#xff1a; 通过拟合一个线性方程来预测连续响应变量。线性回归假设特征和响应变量之间存在线性关系&#xff0c;并通过最小化误差的平方和来优化模型。优点&#xff1a; 简单、直观&#xff0c;易于理解和实现。…

如何用 Cargo 管理 Rust 工程系列 丙

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/viSsCaFR2x9hZOvo1PoRqA 添加依赖项 前面已经提到过在 cargo 配置文件 Cargo.toml 中如何手动添加工程依赖项&#xff0c;cargo 同样提供了 add …

[Linux] Tomcat部署和优化

一、Tomcat相关知识 1.1 Tomcat的简介 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;是 Apache 软件基金会的 Jakarta 项目中的一个核心项目&#xff0c;由 Apache、Sun 和其他一些公司及个人共同开发而成。 …