IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言】零基础教程——动态内存管理 -> 正文阅读

[C++知识库]【C语言】零基础教程——动态内存管理

目录

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

????????1.1我们熟知的开辟内存方式有

?????????1.2已知的开辟方式有以下两个特点

????????1.3原因

?2. 动态内存函数的介绍

????????2.1 malloc

????????malloc函数的特点

????????malloc函数具体使用

????????????????那如果malloc函数开辟失败会怎么样呢?

????????? ? ? ? 在C语言中有这么一个定义好的值:INT_MAX

????????什么是栈区,堆区?

2.2 free

????????先来看看free函数的特点吧~

????????具体应用&&带入刚刚的例子

????????和free前有什么一样的,有什么不一样的?

2.2 calloc

????????calloc函数有什么特点呢?和malloc函数有什么区别呢?

2.3 realloc

????????realloc函数的特点?

????????例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

????????举个例子:

3. 常见的动态内存错误

????????对NULL指针的解引用操作

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

????????对非动态开辟内存使用free释放

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

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

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



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

1.1我们熟知的开辟内存方式有

????????在栈区上开辟4个字节的空间

int i = 0

????????在栈区上开辟16个字节的连续空间

int i[4] = { 0 };

?1.2已知的开辟方式有以下两个特点

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

1.3原因

? ? ? ? 但是,有些时候,我们要通过运行程序才能知道数组需要的空间大小,亦或是在程序运行期间,需要增大数组的空间,这又该如何去做呢?

?????????这就不得不引入动态内存开辟的概念了~


?2. 动态内存函数的介绍

2.1 malloc

????????C语言提供了来自stdlib.h头文件中动态内存开辟的函数

void* malloc (size_t size);

此函数会向?堆区 申请一段连续可用空间,开辟成功则返回开辟空间的起始位置的地址?

malloc函数的特点

? ? ? ? 开辟成功,返回这块空间的指针;

? ? ? ? 开辟失败,返回一个NULL指针,所以每一次开辟空间一定要检查;

? ? ? ? 返回类型为void* ,所以需要我们根据自己的需求,将返回的指针强制类型转化成我们需要的指针;

? ? ? ? 若size的单位是字节,若参数为0,malloc是未定义的,取决于编译器;

malloc函数具体使用

举个例子:开辟10个整形的空间,并分别赋值0~9的数字

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回1表示不正常
	}
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
	return 0;
}

????????这时候可能有同学可能会问了:

????????那如果malloc函数开辟失败会怎么样呢?

演示一波~

? ? ? ? 在C语言中有这么一个定义好的值:INT_MAX

?这是什么东西呢?

? ? ? ? 叫整形最大值,有多大呢?

转到定义来看看:

?不得了,是一个21亿多的一个数字,这时候,让malloc函数开辟这么打一个空间试试?

嗯...好吧没想到电脑内存还蛮大的,开辟出来了...

?这样,咱来个INT_MAX*INT_MAX

走~

?还没运行,就已经提醒你了哈哈哈。

一个开辟空间失败的错误示范;

还有一个点:

什么是栈区,堆区?

? ? ? ? 内存里面分为三个区,就是栈区,堆区,静态区(实际上还有很多其他区,主要讲这几点),栈区是用来存放临时变量的,变量空间定义好就不能再变了,而malloc、calloc、realloc这几个函数就会在堆区上开辟空间,定义好后根据需求,可以继续改变变量所占空间的大小,静态区用来存放全局变量;(如下图)

细致一点就如下图:

?

????????有没有思考过这样一个问题?

? ? ? ?即使一个空间用完了,退出程序,系统会自动回收空间,?但如果一个空间用完了,不想再用了,程序还在需要继续运行,继续开辟空间,那岂不是会造成内存泄露?

2.2 free

? ? ? ? 不用担心~😶?🌫?

? ? ? ? C语言专门提供了一个函数free,用来解决内存泄漏,回收空间的功能!原型如下:

void free (void* ptr);

先来看看free函数的特点吧~

1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。

3.来源于stdlib.h

具体应用&&带入刚刚的例子

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回1表示不正常
	}
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
    free(p);
    p = NULL;
	return 0;
}

这里又是一个细节~

p = NULL?

?想象以下,释放了内存,可以不管这个指针p了吗?

? ? ? ? 经过调试可以看到:

free前:

?注????????意:p,10可以展示出10个元素,若只是p,只会显示一个值;

free后:

和free前有什么一样的,有什么不一样的?

? ? ? ? free后,内存还给操作系统,动态开辟的空间里赋的值全部变为随机数,但是,p指针指向的地址依旧没变(x64环境)。

????????这样会使得指针p十分危险(野指针),即使空间还给了操作系统,但是p地址依旧不变,导致如果以后不小心误用了指针p会导致内存的非法访问!

所以一定要注意,每次释放完动态内存开辟的空间后,请将指针置为NULL;

2.2 calloc

????????C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配

void* calloc (size_t num, size_t size);

calloc函数有什么特点呢?和malloc函数有什么区别呢?

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

区别在于: malloc会将创建好的空间赋上随机值,而calloc 会在返回地址之前把申请的空间的每个字节初始化为全0;

举个例子:

?可以从内存看出calloc函数开辟好空间,内存就已经全部初始化好成全零了

calloc貌似就等于malloc+memset(初始化全零)

所以如果在需要对开辟好的空间初始化成全零,calloc无疑是个很好的选择!

2.3 realloc

? ? ? ? 此函数的出现让动态内存开辟更加灵活!实现了真正意义上的 “动态”~

🤔写代码时,我们常常会遇到这样一个场景,开辟了一个40个字节的空间,但是后来实际操作中,又感觉不够用了,或者是感觉开辟大了,怎么办呢?

????????那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型:

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

realloc函数的特点?

1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

具体来讲realloc在调整内存空间的是存在两种情况:(如下图解)

例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

情况一:

?情况二:

由于上述的两种情况,realloc函数的使用就要注意一些。

举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	p = (int*)realloc(p, 80);//注意这里!这样写可以吗?
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	free(p);
	p = NULL;
	return 0;
}

观察代码中的思考问题,这样写合理吗?

不合理!

????????想象以下,如果realloc空间开辟失败,就会传给p一个空指针,但是p本身是有指向malloc开辟的那块地址啊,一旦接收空指针,原来的那块空间不就废掉了吗?

????????所以可以考虑用个临时指针来接收,如果realloc开辟成功,再将临时指针赋值给p,就可以有效避免内存泄漏问题 ;

代码实现如下:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	int* tmp_p = (int*)realloc(p, 80);
	if (tmp_p != NULL)
	{
		p = tmp_p;
		//处理
	}
	else
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

3. 常见的动态内存错误

对NULL指针的解引用操作

直接上代码:

int main()
{
	int* ptr = (int*)malloc(INT_MAX * INT_MAX);
	*ptr = 10;
	return 0;
}

? ? ? ? 想象以下如果malloc开辟失败会发生什么?

? ? ? ? malloc开辟失败返回一个NULL,赋值给ptr

????????造成对NULL指针的解引用操作!

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

????????看看以下代码哪里出问题了?

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(ptr + i) = i;
	}
    free(ptr);
    ptr = NULL;
	return 0;
}

很明显,当赋值10的时候发生了越界访问;

对非动态开辟内存使用free释放

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*ptr = i;
		ptr++;
	}

	free(ptr);
	ptr = NULL;

	return 0;
}

?这样是否可行?

????????ptr一旦++,导致ptr指针不在指向动态开辟的起始位置,这时候free会发生什么呢?(如下图)

这时候有人可能会说:那用const修饰以下不就很安全吗?

????????是啊,安全是安全了,但是ptr = NULL;不也不行了吗?从而产生野指针问题,所以一定要注意避免!

?

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

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

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

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    free(ptr);

    //处理

    free(ptr);//重复释放
    return 0;
}

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

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    return 0;//忘记free
}

注????????意:动态开辟的空间一定要释放,并且正确释放!

?

码字不易~

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:02:42  更:2022-07-17 16:06:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:08:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码