C语言笔记(动态空间的内存管理)

发布于:2024-12-06 ⋅ 阅读:(112) ⋅ 点赞:(0)

目录

1.动态内存函数

1.1.malloc和free函数

1.2.calloc函数

1.3.realloc函数

1.4.总结

2.常见的动态内存错误

2.1.对NULL指针的解引用操作

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

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

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

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

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

2.7.总结


前言:为什么要有动态内存管理呢?

        之前我们在学习创建变量、数组,等等都是申请空间来放置数值;一旦申请之后,空间的大小不会再改变。本文将介绍C语言中申请内存大小的函数 malloc、calloc、realloc、free,以及对内存函数使用时候常见的错误。若有错误,请各位指正。

1.动态内存函数

        动态内存函数是针对堆区来说的,下图是内存的大致划分,静态内存就是静态区中的全局变量和静态修饰的局部变量。如下代码是static修饰的局部变量,属于静态区。

static a = 0;

1.1.malloc和free函数

1.功能:

  malloc:向内存申请一块连续可用的空间,返回指向这块空间起始的指针;

  free:   释放动态开辟的内存。

2.使用形式

void* malloc (size_t size);
void free (void* ptr);

malloc函数

1.void* 返回值类型为空指针,需要强转换再使用,申请成功返回内存块起始指针,申请内存失败,返回位空指针;

2.size 内存块的大小,字节位单位,size_t无符号整型;

注意:

1.malloc函数申请的空间是不会初始化的,内存块不初始化是随机值。

2.在free释放内存块之后,void* 函数的返回值保留之前的开辟内存块的时候指针变量,

如果再访问就是非法访问了,因此使用完内存空间要将返回值置为NULL,可看示例。

free函数

1.viod没有返回值;

2.ptr 指向先前使用或者分配的内存块的指针,如果ptr是空指针NULL,则函数不会执行任何操作。

注意:free函数的形式参数(ptr)是指向动态内存空间的变量。

3.示例

(1)正常使用

#include <stdio.h>
#include <stdlib.h>
int main ()
{
    //申请动态内存空间
	int* p = (int*)malloc(40);
	//判断是否申请成功,并强制转换为整型
	if (p == NULL)
	{
		perror("malloc");
		return 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;
	//用完之后返回给堆区,以便下次的调用
	free(p);
	p = NULL;
}

(2)申请错误的案例

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//申请动态内存空间
	int* p = (int*)malloc(INT_MAX);//INT_MAX宏定义,表示int中最大的值2147483647
    //16进制是0x7FFFFFFF
	//判断是否申请成功,并强制转换为整型
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	p = NULL;
}

        INT_MAX问宏定义,表示int 整型中最大的值,2147483647,开辟空间可能已经超过给函数分配的空间,输出的结果是没有足够的空间。

(3)越界访问示例

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//申请动态内存空间
	int* p = (int*)malloc(40);
	//判断是否申请成功,并强制转换为整型
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	p = NULL;
		//赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
}

这种情况下是无法写入的。

(4)free函数释放非动态空间

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int a = 0;
	int* p = &a;//创建非动态空间的指针
	free(p);
	
}

        释放非动态空间的指针会直接报错的。

1.2.calloc函数

1.功能:Allocate and zero-initialize array分配内存并将其初始化为0.

2.使用形式:

void* calloc (size_t num, size_t size);

1.void* 同上;返回值是指向调整之后内存的起始位置;

2.num:分配元素的个数;;

3.size:每个元素的大小;

注意:

1.calloc函数和malloc函数不同,申请空间之后会将其 初始化为0;其余的作用基本一样。

3.示例

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	int* p2 = (int*)calloc(10 ,sizeof(int));//两者的表达方式相似,只是*改为了,
	//判断是否申请成功
	if (p1 == NULL)
	{
		perror("malloc");
		return 1;
	}
	else if (p2 == NULL)
	{
		perror("calloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p2 + i) = i;
	}
		//输出
	for (i = 0; i < 10; i++)
	{
		printf(" %d",*(p2 + i) ); 
	}
	free(p1);
	free(p2);
	p1 = NULL;
	p2 = NULL;
	return 0;
}

        其中p1是malloc函数申请的内存,p2是calloc申请的内存,可以看出calloc函数在申请完内存之后就会初始化为0.

1.3.realloc函数

1.功能:Reallocate memory block,重新分配内存块的大小,扩展原有的内存块,或者申请新的内存块,

特点:保留之前的内存中的数据

2.使用形式:

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

1.void* 同上,返回值是指向调整之后内存的起始位置;

2.ptr     先前使用或者分配的内存块的大小;

3.内存块的大小。

注意:

1.和malloc一样不会初始化空间;

2.申请空间失败的将返回NULL空指针;

3.如果ptr是空指针,则该函数的行为类似于 malloc,分配一个新的字节块并返回一个指向其开头的指针;

4.realloc函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

5.realloc函数是重新申请内存块,不是追加在malloc内存空间的内存块,是将原本的空间扩展到size的大小,整个内存块空间的大小就是size,和malloc再开辟的空间相比,函数只是可以保留剩之前已经赋值的内存空间。

4.realloc调整内存空间的两种情况

        假设 malloc函数申请了5个整型的空间,但是不够是使用了,要是使用 realloc函数将malloc 的空间扩展为10个整型的空间,可以分为两种情况。假设其返回值分别为p1和p2。

(1)p1==p2的情况

        malloc申请的空间可以扩容,可以在后面直接续上新的空间,malloc函数返回值和realloc函数的返回值是一样的。

(2)p1!=p2的情况

        当malloc申请的空间后面没有足够的空间扩容,realloc函数会重新昭仪满足空间大小的连续的空间把就得空间数据,拷贝到新空间的对应的位置,并把旧的空间释放掉,同时返回新的内存块的起始位置。

3.示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(5*sizeof(int));//申请5个整型数据的空间
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	int* str = realloc(p, 10* sizeof(int));//重新申请的内存空间
	if (str == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		p = str;//情况1,空间已经被释放了,p=str;
				//情况2, 新开辟的内存空间,之前malloc开辟的空间已经释放,p不为NULL
				//如果统一使用,建议使用p
	}
	for (; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf(" %d", *(p + i));
	}
	free(str);//realloc函数延续之前分配的空间继续使用或者释放掉之前的空间,
	p = NULL;
	str = NULL;
	return 0;

}
1.4.总结

        malloc函数和realloc函数申请的函数空间不会初始化,calloc申请的函数空间会初始化,realloc申请的空间是在malloc和calloc空间后面的追加,free配合前面的三个函数使用。动态内存函数所设计到的操作系统的设置我不会啊,所以没有能力模拟函数实现。

2.常见的动态内存错误

2.1.对NULL指针的解引用操作
#include <stdio.h>
#include <stdlib.h>
void test()
{
	int *p = (int *)malloc(INT_MAX );
	*p = 20;//如果p的值是NULL,就会有
	free(p);
}
int main()
{
	test();
	return 0;
}

        上述的代码中malloc函数没有申请成功内存空间,也不会报错出错误在哪里,因此在对返回指针进行解引用操作的之前需要判断是否为空指针。

2.2.对动态开辟的空间的越界访问
#include <stdio.h>
#include <stdlib.h>
int main ()
{
    //申请动态内存空间
	int* p = (int*)malloc(100);
	//判断是否申请成功,并强制转换为整型
	if (p == NULL)//判断是否为空指针
	{
		perror("malloc");
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i < 100; i++)
	{
		*(p + i) = i;
	}
	//用完之后返回给堆区,以便下次的调用
	free(p);
	p = NULL;
	return 0;
}

如果越界访问,程序就会挂了,表现得现象就是程序窗口光标

2.3.对非动态开辟内存使用free释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int a = 10;
	int *p = &a;
	free(p);
	p = NULL;
	return 0;
}

会报错的

2.4.使用free释放一块动态开辟内存的一部分
#include <stdio.h>
#include <stdlib.h>
int main()
{
	    //申请动态内存空间
		int* p = (int*)malloc(100);
		//判断是否申请成功,并强制转换为整型
		if (p == NULL)
		{
			perror("malloc");
			return 1;
		}
		//赋值
		int i = 0;
		for (i = 0; i < 25; i++)
		{
			*p = i;
			p++;
		}
		//用完之后返回给堆区,以便下次的调用
		free(p);
		p = NULL;
		return 0;	
}
2.5.对同一块动态内存多次释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
	    //申请动态内存空间
		int* p = (int*)malloc(100);
		//判断是否申请成功,并强制转换为整型
		if (p == NULL)
		{
			perror("malloc");
			return 1;
		}
		free(p);
		
		//用完之后返回给堆区,以便下次的调用
		free(p);
		p = NULL;
		return 0;	
}
2.6动态开辟内存忘记释放(内存泄漏)
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i = 1;
	int* p=&i;
	while (i--)
	{
		p = (int*)malloc(20);
	}
	free(p);
	return 0;
}

上述的案例是申请了一次的内存块,电脑运行的内存几乎没有变化。

内存不释放的情况下,

#include <stdio.h>
#include <stdlib.h>
int main()
{
	while (1)
	{
		malloc(20);
	}
	return 0;
}

        运行上述程序之后,内存的会增大的,截取不到变化的之后的数值。

         程序的设计限制了开辟内存空间的大小,如果不限制,程序一直运行下造成的内存泄漏,会导致计算机的性能下降,系统不稳定,资源的浪费等等。

2.7.总结

1.内存空间的开辟之后需要对返回的指针判断是否为NULL;

2.申请的内存空间要满足所存储数据的大小,不可访问越界;

3.内存申请函数和free函数配合使用,且释放之后将动态内存函数的返回值置为NULL;

4.不可释放多次内存空间;

5.动态内存使用完毕,一定要释放空间。


网站公告

今日签到

点亮在社区的每一天
去签到