详解C语言之动态内存管理

动态内存管理

💓 博客主页:C-SDN花园GGbond
⏩ 文章专栏:玩转c语言

1. 静态开辟内存

通过前面的学习,我们已经掌握了静态开辟内存:

#include<stdio.h>
int main()
{
	int val = 20; //在栈空间上开辟四个字节
	char arr[10] = { 0 }; //在栈空间上开辟10个字节的连续空间
	return 0;
}

但是静态开辟的空间明显有两个缺陷:

1.空间开辟⼤⼩是固定的。
2.数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整。

2. 动态内存

了解决静态内存开辟的内存空间固定的问题,C语言引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了

2.1 动态内存开辟函数

(1) malloc函数

  1. 头文件#include <stdlib.h>
    2.声明:void* malloc (size_t size);
    size – 内存块的大小,以字节为单位
    如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。
    3.作用:向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针
    如果开辟成功,则返回⼀个指向开辟好空间的指针。
    如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
    4.返回值:返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。

补充打印错误信息函数:perror()

1.头文件:#include <stdio.h>
声明:void perror(const char *str)
str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。
2.作用:把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
3.返回值:无返回值。

下列是malloc与perror的具体使用方法:

int main()
{
	int* arr = (int*)malloc(sizeof(int) * 10);
	//开辟十个大小为整型的空间
	//返回类型强转为int*
	if (arr == NULL)//如果开辟失败
	{
		perror("malloc fail: ");//打印错误信息
                 return 1;//直接返回
	}
	int i = 0;
	for (i = 0; i < 10; i++)//存入数据
	{
		arr[i] = i;
	}
	for (i = 0; i < 10; i++)//打印数据
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出结果:
在这里插入图片描述
内存观察:
在这里插入图片描述
动态内存的数据存放在堆区

(2) calloc函数

1.头文件:#include <stdlib.h>

2.声明:void *calloc(size_t nitems, size_t size)

nitems – 要被分配的元素个数。 size – 元素的大小。 作用: 分配所需的内存空间,并返回一个指向它的指针

3.返回值:该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL。

malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零
下列是calloc的使用实例:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//开辟十个大小为整型的空间
	//返回类型强转为int*
	if (arr == NULL)//如果开辟失败
	{
		perror("calloc fail: ");//打印错误信息
                 return 1;//直接返回
	}
	return 0;
}

calloc内存观察:
在这里插入图片描述
(3) realloc函数

1.头文件:#include <stdlib.h> 声明:void *realloc(void *ptr, size_t size) ptr –
2. 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc进行分配内存的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
size – 内存块的新的大小,以字节为单位。如果大小为0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。 作用:尝试重新调整之前调用 malloc 或calloc 所分配的 ptr 所指向的内存块的大小。
3.返回值:该函数返回一个指针 ,指向重新分配大小的内存。如果请求失败,则返回NULL。

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

2.realloc扩容机制:
本地扩容:原有空间之后有⾜够⼤的空间,直接在原有内存之后直接追加空间,原来空间的数据不发⽣变化。
在这里插入图片描述

异地扩容:原有空间之后没有⾜够⼤的空间,在堆空间上另找⼀个合适⼤⼩的连续空间。将新增数据与原本数据拷贝过来,并自动释放原来空间

在这里插入图片描述
下列是realloc的具体使用方法:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//开辟十个大小为整型的空间
	//返回类型强转为int*
	if (arr == NULL)//如果开辟失败
	{
		perror("calloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	//继续新增空间
	int* tmp = (int*)realloc(arr, sizeof(int) * 15);
        //不用arr是为了防止开辟失败,被至为NULL
	if (tmp == NULL)//如果开辟失败
	{
		perror("realloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	arr = tmp;
	return 0;
	
}

在这里插入图片描述
新增内存较小时一般是在原有基础上新增空间。两者地址相同。

新增内存较大时则会重新开辟一段空间,将原来的空间释放。两者地址不同
**

2.2 动态内存释放函数

**

动态内存开辟的空间并不像静态开辟内存的空间会随着一段程序的结束而回收,这时就需要我们手动回收,否则就会造成内存泄漏

**内存泄漏(Memory Leak)**是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

1.头文件:#include <stdlib.h>
2.声明:void free(void *ptr)
ptr – 指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。
3.作用:释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
返回值:该函数不返回任何值。

下面使用free函数的实例:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//开辟十个大小为整型的空间
	//返回类型强转为int*
	if (arr == NULL)//如果开辟失败
	{
		perror("calloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	//继续新增空间
	int* tmp = (int*)realloc(arr, sizeof(int) * 100);
 
	if (tmp == NULL)//如果开辟失败
	{
		perror("realloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	arr = tmp;
	free(arr);//释放arr所指向的内存
	arr = NULL;
	return 0;
	
}

**释放完之后记得将arr置为NULL,否则arr指向一段已经回收的空间会变成野指针。

2.3 常见内存分布

⼀般我们在学习C/C++语⾔的时候,我们会关注内存中的三个区域:栈区、 堆区、静态区。

1.局部变量与函数参数是放在内存的栈区,

2.全局变量,static修饰的变量是放在内存的静态区。

3.堆区是⽤来动态内存管理的
具体分布如下图:
在这里插入图片描述

3. 动态内存的常见错误

动态内存开辟就像指针一样,一不小心就会酿成大错,以下介绍了一些常见的内存开辟错误:

3.1 对NULL指针的解引用

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

1.INT_MAX是一个宏定义,他表示整型的最大值,值为2147483647。
2.当malloc申请的空间太大时存在失败的情况,失败返回NULL指针。
3.而系统无法访问NULL指针指向的地址,这时编译器会报一个警告:
在这里插入图片描述
改正方法:

void test()
{
    int* p = (int*)malloc(INT_MAX / 4);
    if (NULL == p)
    {
        perror("malloc fail: ");//打印错误信息
        return 1;
    }
    *p = 20;
    free(p);
    p = NULL;
}

这时就体现判断是否为空指针的重要性了

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

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i; //当i是10的时候越界访问
	}
	free(p);
         p=NULL;
}

1.malloc只申请了十个整型大小的空间。
2.for循环循环了十一次,越界访问,错误信息如下:

在这里插入图片描述
改正方法:

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail: ");//打印错误信息
		return 1;//直接返回
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i; //当i是10的时候越界访问
	}
	free(p);
	p = NULL;
}

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

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
         p=NULL;//ok?
}

1.free()只能释放有动态内存开辟在堆上的空间。
2.p指向的空间是静态内存开辟的,无法释放,释放就会出错:
!在这里插入图片描述

void test()
{
	int a = 10;
	int* p = &a;
}

静态内存开辟的空间并不需要释放。

3.4 使⽤free释放⼀块动态开辟内存的⼀部分

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

1.p++跳过一个整型大小的空间。
2.free()释放p只会释放当前位置开始之后的空间,有一个整型大小的空间未被释放,造成内存泄漏。
在这里插入图片描述
改正:
void test()
{
int* p = (int*)malloc(100);
free§;
p = NULL;
}

3.5 对同⼀块动态内存多次释放

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

1.p已经被释放归还给操作系统,但是此时p还指向该内存,是一个野指针。
2.再次释放p就会出现内存出错问题。
在这里插入图片描述
改正:

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	p = NULL;
}

释放内存之后记得将其置为空指针,这样再次free空指针就不会进行任何操作。

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

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}//内存泄漏
}
 
int main()
{
	test();
}

当我们动态内存申请空间之后必须手动将其释放,不会就会出现内存泄漏的问题。
改正方法:

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

每次使用完动态内存开辟空间之后记得释放内存。

4. 相关笔试题

未完待补

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值