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)静态存储区:?

(2)栈区:

(3)堆区:

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

三、动态内存函数的介绍

(1)malloc函数

?函数作用:

函数参数:

函数使用:?

(2)free函数?

函数作用:

函数参数:

函数使用:?

(3)calloc函数

?函数作用:

函数参数:

函数使用 :

(4)realloc函数

函数作用:

函数参数:

函数使用 :

四、常见动态内存错误

(1)、对NULL指针的解引用操作

(2)、对动态开辟空间的越界访问

?(3)、对非动态开辟内存使用free释放

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

(5)、对同一块动态内存多次释放?

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


说到内存管理,我们要先了解一下什么是内存:

一、内存分配——静态存储区,栈区,堆区

内存的基本构成:

可编程内存在基本上分为这样几大部分:静态存储区,堆区和栈区,他们的功能不同,对他们的使用方式也就不同。

(1)静态存储区:?

主要存放static静态变量、全局变量、常量。这些数据内存在编译的时候就已经为他们分配好了内存,生命周期是整个程序从运行到结束。

(2)栈区:

存放局部变量。在执行函数的时候(包括main这样的函数),函数内的局部变量的存储单元会在栈上创建,函数执行完自动释放,生命周期是从该函数的开始执行到结束。

(3)堆区:

程序员自己申请一块任意大小的内存—也叫动态内存分配。这块内存会一直存在知道程序员释放掉。C语言中,用malloc or new 动态地申请内存,用free?or?delete释放内存。良好习惯:若申请的动态内存不再使用,要及时释放掉,否则会造成内存泄露。

内存空间具体划分如下:(之后会重点讲堆区的动态内存开辟)

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

我们已经掌握和了解到的内存开辟方式是通过数据类型来定义变量,然后操作系统在栈区、静态区或者字符常量区上为该变量分配空间

int a;         //在栈区上为 a 变量分配4个字节的空间
char arr[10]={0};  //在栈区上为 arr 变量分配10个字节连续的空间
static int c;  //在静态区上为 c 变量分配4个字节的空间
char* p = "abcdef";  //在栈区上为 p 变量分配4/8个字节的空间,在字符常量区上为常量字符串分配空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。

三、动态内存函数的介绍

(1)malloc函数

?函数作用:

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

函数参数:

void* malloc (size_t size);
# void* 函数返回值,申请成功返回指向开辟的空间的指针,申请失败则返回NULL;
# size_t size 参数,指定要开辟多少个字节的空间;

函数使用:?

#include <stdio.h>
#include <stdlib.h>  //动态内存管理对应头文件
#include <string.h>  //strerror对应头文件
#include <errno.h>   //errno对应头文件

int main()
{
	//申请20个字节的空间,交由指针变量p来管理
	int* p = (int*)malloc(5 * sizeof(int));
	//malloc申请空间可能会失败,所以要进行判断
	//申请失败:打印错误信息并退出
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));//打印错误信息
		return 1;
	}
	//申请成功:使用
	for (int i = 0; i < 5; i++)
	{
		p[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	//使用完:释放
	free(p);   //释放动态内存开辟的空间
	p = NULL;  //将p置空,防止野指针
}

结果如下:?

malloc函数其实有两种写法:

int *ptr = (int*)malloc(20);
int *ptr = (int*)malloc(5*sizeof(int));

malloc函数使用细节:

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

(2)free函数?

? ? ? ?free函数主要是用于在堆区动态内存开辟的一个空间,需要程序员主动调用这个函数来释放空间,堆区不会自己释放空间,而我们一旦使用动态内存开辟的函数,比如malloc、realloc、calloc 开辟空间使用完忘记释放时,就会造成内存泄露相当于你向内存申请了一块空间,但是你使用完之后不归还,这样别人也用不了这块空间了,虽然这块空间还存在,但是相当于没有了),然后就会发现随着程序在不断运行,可供使用的空间将会越来越少,所以说,内存泄漏是我们在动态内存开辟中会犯的一个常见的错误。

? ? ? 不过,当我们电脑关机时,操作系统会自动回收动态内存开辟的空间,这就是说为什么电脑关机可以解决大部分问题。

函数作用:

用来释放开辟的动态内存

函数参数:

void free (void* ptr);
# void* ptr 你要释放的空间的起始地址;

函数使用:?

在使用free函数时,前面一定要有动态内存开辟的函数,比如malloc、realloc、calloc。如果参数ptr不是指向动态开辟的,那么free函数是未定义的。

(3)calloc函数

?函数作用:

calloc 函数的功能和 malloc 十分相似,都是向堆区申请一块空间并返回空间的起始地址,但是 calloc 函数比 malloc 函数多了一个操作,那就是会将申请的空间里面数据全部初始化为0。

函数参数:

void* calloc (size_t num, size_t size);
# void* 函数返回值,申请成功返回动态开辟的空间的起始地址,申请失败则返回NULL;
# size_t num 函数参数,用于指定要申请的元素个数:
# size_t size 函数参数,用于指定每一个元素的大小(字节为单位);

函数使用 :

#include <stdio.h>
#include <stdlib.h>  //动态内存管理对应头文件
#include <string.h>  //strerror对应头文件
#include <errno.h>   //errno对应头文件

int main()
{
	//申请20个字节的空间,交由指针变量p来管理
	int* p = (int*)calloc(5, sizeof(int));
	//calloc申请空间可能会失败,所以要进行判断
	//申请失败:打印错误信息并退出
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//申请成功:使用
	for (int i = 0; i < 5; i++)
	{
		p[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	//使用完:释放
	free(p);   //释放动态内存开辟的空间
	p = NULL;  //将p置空,防止野指针
}

结果如下:?

(4)realloc函数

函数作用:

调整已开辟的动态空间的大小

函数参数:

void* realloc(void* ptr, size_t size);
# void* 函数返回值,开辟成功返回动态开辟的空间的起始地址,开辟失败则返回NULL;
# void* ptr 函数参数,表示要调整的空间的起始地址;
# size_t size 函数参数,新的空间的大小;

函数使用 :

#include <stdio.h>
#include <stdlib.h>  //动态内存管理对应头文件
#include <string.h>  //strerror对应头文件
#include <errno.h>   //errno对应头文件

int main()
{
	//先开辟一块空间
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	//扩容
	//由于realloc可能会开辟失败,为了防止p指向realloc开辟失败的空间,从而丢失原来空间的情况,这里我们使用临时变量接受realloc的返回值
	int* ptr = (int*)realloc(p, 10 * sizeof(int));
	//申请失败:打印错误信息并退出
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//申请成功:让p指向该空间并使用
    p = ptr;
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//使用完:释放
	free(p);   //释放动态内存开辟的空间
	p = NULL;  //将p置空,防止野指针
}

?结果如下:

realloc函数注意事项:

1、有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整;realloc 函数就可以做到对动态开辟内存大小的调整;

2、realloc 函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间;

3、当 realloc 函数的第一个参数为NULL时,realloc 当作 malloc 函数使用;

realloc在调整内存空间的时候会存在两种情况:

情况 1
原有空间的后面有足够大的空间,可以让我们申请。这时扩展内存就在原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2
原有空间之后没有足够多的空间时,这时 realloc 函数会在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,所以我们在使用 realloc 函数的时候不要直接将重新调整的空间地址直接赋值给源空间地址,而是应该先进行空指针判断,避免开辟失败的同时还将源空间搞丢,造成内存泄漏;

四、常见动态内存错误

(1)、对NULL指针的解引用操作

malloc、calloc、realloc 这些函数向内存申请空间是有可能会失败的,申请失败函数就会返回空指针,如果我们不对函数的返回值进行判断,而直接对其解引用的话,就会造成程序崩溃;例如:

void test()
{
	int* p = (int*)malloc(INT_MAX);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

(2)、对动态开辟空间的越界访问

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

?(3)、对非动态开辟内存使用free释放

void test()
{
	int a = 10;
	int* p = &a;  //在栈区上开辟空间
	free(p);
}

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

当我们成功开辟一块动态空间并将它交由一个指针变量来管理时,我们可能会在后面的程序中让该指针变量自增,从而让其不再指向该动态空间的起始位置,而是指向中间位置或者结尾,这时我们在对其进行free操作时,也会导致程序崩溃,因为free函数必须释放一整块动态内存,而不能释放它的一部分。

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;  //指针变量p自增导致其丢失动态内存的起始地址
	}
	free(p);
}

(5)、对同一块动态内存多次释放?

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

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

下面我举一个可能造成内存泄漏的经典案例:

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

没有用free函数导致内存泄漏?,

切记:
动态开辟的空间一定要释放,并且正确释放 。


?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:22:11  更:2022-10-08 20:24:26 
 
开发: 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年5日历 -2024/5/19 3:42:56-

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