C语言中动态内存分配malloc、calloc和realloc函数解析

 更新时间:2022年03月10日 11:15:20   作者:寄一片海给你  
C语言跟内存申请相关的函数主要有 alloca、calloc、malloc、free、realloc等,下面这篇文章主要给大家介绍了关于C语言中动态内存分配malloc、calloc和realloc函数的相关资料,需要的朋友可以参考下

前言

有时候我们需要的空间大小不确定,需要随着程序需要的空间而变化, 那以数组开辟的固定大小的空间就不适用了, 这时候我们就需要动态分配开辟空间了。当空间不够时就扩容。动态开辟是在堆区开辟一块连续可用空间,并返回这块空间的地址。有三种函数malloc, calloc和realloc。我们动态内存分配就在堆区开辟空间

上面的四个区只有堆区的空间是需要手动释放的

free函数

free函数是专门用来对动态开辟内存的回收和释放的。当我们不需要再使用动态开辟的空间时,一定要free释放空间,因为是在堆上开辟的空间,所以不会随着出了作用域而销毁,需要我们free释放,避免内存泄漏,并置空(将指针置为NULL),避免形成野指针。

内存释放是标记删除, 只会修改当前空间的所属状态,并不会清除空间内容。

当然内存泄漏也不是都有危害,但为了养成良好的代码习惯,动态开辟后一定要free

void free (void* ptr);

1.如果参数ptr指向的内存空间不是动态内存开辟的,那free函数的行为是未定义的,就会报错,所以free函数是针对动态开辟的空间

2.如果ptr是 NULL空指针,那么free函数什么都不做。

下面我们来实现一下。

动态内存分配需要调用头文件#include<stdlib.h>

malloc函数

void* malloc (size_t size);

malloc函数用来动态开辟的, size是开辟所需空间的大小,单位:字节。

并返回起始地址。返回类型是void*,表示我们可以开辟任意类型的空间,同时我们需要强制类型转换成我们开辟空间类型的指针,再用指针接收

例如我们开辟的是int型的空间,就需要先转换为(int*),再用int*的指针接收

开辟float型的空间,就需要先转换为(float*),用于float*的指针接收

int* p = (int*)malloc(sizeof(int) * 10);

重点:

1.malloc函数在开辟空间后需要判断开辟空间是否成功,若开辟成功会返回开辟好的空间的指针,开辟失败会返回空指针。

2.malloc函数并不会对开辟空间进行初始化,空间内容为随机数。

下面是关于malloc函数的例子,主函数如下:

主函数中第一段代码就是在堆上动态开辟10个int型大小的空间,通过int*型的指针p去维护这块空间,如果空间开辟失败就会返回NULL,所以我们需要判断是否开辟成功。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{	
	int* p = (int*)malloc(sizeof(int) * 10);
	if (p == NULL)
	{                                     //我们也可以直接eixt(-1)异常退出,或是return;
		printf("%s\n", strerror(errno));  //strerror(errno)是用来判断开辟失败的错误原因                               
		return -1;                        //error是错误码
	}                                     //需要调用最下面的两个头文件
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
	return 0;
}

判断p为NULL时,也可以直接打印开辟失败,不需要调用strerror函数,也不用包含那两个头文件

if (p == NULL)
{
	printf("malloc failed\n");
	exit(-1);
}

calloc函数

void* calloc (size_t num, size_t size);

calloc函数与malloc类似 ,calloc可以看作malloc+memset

 参数num是开辟空间的元素个数,参数size是元素的大小,单位:字节。

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

calloc函数与malloc函数不同点在于:会将开辟的空间都初始化为0(按字节初始化为0)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{	
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return -1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
	return 0;
}

下面看运行结果都为0

realloc函数

realloc函数让动态内存管理变得更加灵活,空间不够时可以对动态开辟的空间扩容

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

参数ptr为需要扩充动态内存分配的空间的地址

size是 调整之后新大小,返回参数为调整之后的内存起始位置。

realloc在调整内存空间的是存在两种情况:

1.原地扩容

在需要扩容的空间后有足够的空间进行扩容,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

2.异地扩容

原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

pc指向的空间的数据会迁移到ptr指向的空间,此时不需要将pc的空间free,系统会自动释放。

下图通过两行代码了解什么是异地扩容,可以看到p1和p2的地址发生了变化

p1的值是0x006b56b8  p2的值是0x006bf4d0,所以扩容后的空间地址与原空间地址不同,这就是异地扩容

当然扩容失败就会返回NULL,异常退出了

下面是malloc和realloc的联合使用,我们扩容后要将扩容后的空间再次交给原指针去维护

所以将p=ptr

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	int* p =(int*) malloc(sizeof(int)*10);
	if (p == NULL)
	{
		printf("malloc faied\n");
		return -1;
	}
	int* ptr = (int*)realloc(p, sizeof(int) * 20);
	if (ptr == NULL)
		return -1;
	p = ptr;
	for (int j = 0; j < 20; j++)
	{
		printf("%d ", p[j]);
	}
 
	printf("\n");
    
    return 0;
}

运行结果可以看到都是随机数,所以realloc函数也不会初始化

可以看出realloc与malloc相似,都不会初始化。同时这也是realloc和malloc的一个特性,

当要扩容的对象为空时,realloc可以当作malloc函数使用。

扩充

我们一般动态开辟的空间都是结构体,下面简单介绍一下开辟结构体类型的空间

我们首先定义了一个结构体类型,里面定义的是int型的数据data,还有一个next型的指针,存放下一节点的地址,这就是数据结构的单链表结构,不了解的小伙伴可以简单看一下

pc指向开辟的一个节点用,p指向开辟的另一个节点用,将pc指向的结构体中的next保存p指向节点的地址

#include <stdio.h>
#include <stdlib.h>
 
typedef struct QueueNode
{
	DataType data;
	struct QueueNode* next;
}QueueNode;
 
int main()
{
	QueueNode* pc = (QueueNode*)malloc(sizeof(QueueNode));
	QueueNode* p = (QueueNode*)malloc(sizeof(QueueNode));
 
	if (pc == NULL && pc == NULL)
	{
		printf("malloc failde\n");
		exit(-1);
	}
	pc->data = 10;
	pc->next = p;
 
	p->data = 20;
	p->next = NULL;
 
	printf("%d %d\n", pc->data, p->data);
	printf("%p %p %p\n", pc->next, p, p->next);
	free(pc);
	free(p);
	pc = p = NULL;
 
	return 0;
}

重点:动态开辟使用完后,一定要记得free,置空。

了解完上面的内容,是不是对动态分配有了更深的理解。

malloc/calloc/realloc区别总结

相同点:

1.都是从堆上申请空间

2.都需要对返回值判空

3.都需要用户free释放

4.返回值类型相同(void*)

5.都需要类型转化

6.底层实现上是一样的,都需要开辟多余的空间,用来维护申请的空间

可以输入以下代码观测内存:

#include <stdio.h>
#include <malloc.h>
int main(){
int *p= (int *)malloc(sizeof(int )*10);
return 0;
}

不同点:

1.函数名字不同和参数类型不同。

2.calloc会对申请空间初始化,并且初始化为0,而其他两个不会。

3.malloc申请的空间必须使用memset初始化

4.realloc是对已经存在的空间进行调整,当第一个参数传入NULL的时候和malloc一样

总结

到此这篇关于C语言中动态内存分配malloc、calloc和realloc函数解析的文章就介绍到这了,更多相关C语言动态内存分配函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++11 强类型枚举相关总结

    C++11 强类型枚举相关总结

    这篇文章主要介绍了C++11 强类型枚举的相关资料,帮助大家更好的理解和学习使用c++11,感兴趣的朋友可以了解下
    2021-02-02
  • MFC自绘Button按钮分析和实现

    MFC自绘Button按钮分析和实现

    这篇文章主要为大家详细介绍了MFC自绘Button按钮分析和实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • Qt中正则表达式的常见用法(QRegularExpression类)

    Qt中正则表达式的常见用法(QRegularExpression类)

    正则表达式即一个文本匹配字符串的一种模式,Qt中使用QRegExp类进行模式匹配,下面这篇文章主要给大家介绍了关于Qt中正则表达式的常见用法,文中介绍的是QRegularExpression类的相关资料,需要的朋友可以参考下
    2024-05-05
  • C语言 推理证明带环链表详细过程

    C语言 推理证明带环链表详细过程

    单链表中同样也有具有挑战性的题目,链表的带环问题可以说是众多难题中的佼佼者,在这里可能更看重的是逻辑推理和证明的过程
    2022-04-04
  • C语言字符串数组详解

    C语言字符串数组详解

    这篇文章主要介绍了C语言字符串数组详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • C++人工模拟栈实现方法

    C++人工模拟栈实现方法

    在本篇内容里小编为大家整理了关于C++人工模拟栈实现方法和步骤,需要的朋友们可以学习下。
    2018-12-12
  • C/C++ Qt 自定义Dialog对话框组件应用案例详解

    C/C++ Qt 自定义Dialog对话框组件应用案例详解

    有时候我们需要一次性修改多个数据,使用默认的模态对话框似乎不太够用,此时我们需要自己创建一个自定义对话框。这篇文章主要介绍了Qt自定义Dialog对话框组件的应用,感兴趣的同学可以学习一下
    2021-11-11
  • C++常用函数之XML JSON格式转换问题

    C++常用函数之XML JSON格式转换问题

    XML在Json出现前应用很广泛,灵活性好,应用语言也没有限制,发展了这么长时间后xml标准已经很臃肿。这篇文章主要介绍了C++常用函数之XML JSON格式转换问题,需要的朋友可以参考下
    2020-02-02
  • 总结C语言中const关键字的使用

    总结C语言中const关键字的使用

    一起虽然学过c语言,但是并没有写过太多的代码,最近想要拾起c语言,就写了一些代码,但是对const关键字比较陌生,这里总结一下,方法自己和大家有需要的时候参考借鉴,下面跟着小编一起学习学习吧。
    2016-11-11
  • C++编程产生指定范围内的随机数

    C++编程产生指定范围内的随机数

    这篇文章主要为大家详细介绍了C++编程产生指定范围内的随机数,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09

最新评论