动态内存管理详解

在进行学习之前,首先我们要明白为什么存在动态内存分配,它到底在一段程序中扮演什么角色?

动态内存管理详解_第1张图片

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了, 这个时候就需要用到动态内存分配啦;

一.首先我们来到动态内存函数的介绍:

mallocfree

两个函数的头文件都是 #include

先来认识malloc函数:

malloc函数的功能当然就是分配内存块啦,向内存申请一些连续可用的空间

调用malloc函数时它会分配一个由 sizeof(type) 字节组成的内存块,返回一个指向该块开头的指针。 

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

让我们通过代码与图片深入了解一下malloc函数:

动态内存管理详解_第2张图片

 那么此处就存在一个问题,有些大型程序是一天24小时不断运行的,如果一直不退出程序,那么malloc就不会释放内存,那么这就很容易引起内存泄漏

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

那怎么办呢? 这就需要用到接下来介绍的函数啦;

解决这一问题的主角——free函数:

free函数用来释放动态开辟的内存 ;

先前通过调用malloc、calloc或realloc分配的内存块将被释放,使其再次可用于进一步的分配。

free函数使用时的注意事项:
. 如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
. 如果参数 ptr NULL 指针,则函数什么事都不做。

动态内存管理详解_第3张图片

 此处的a不是动态开辟的,不能使用free来释放a的空间;

让我们通过完整代码来看看malloc和free到底怎么搭配使用的:

#include
#include

int main() {
	//int arr[10];  下面我们用动态内存开辟的方法来创建
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");//开辟失败返回错误
		return 1;
	}
	//开辟成功进行打印
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}

	free(p);//释放空间后并不会主动将p置为NULL
	p = NULL;//释放完p的空间后手动将p置为NULL

	return 0;
}

接下来就是同样具有开辟空间功能的函数calloc:

calloc函数的功能是分配和零初始化数组; 

为num个元素的数组分配一块内存,每个元素的大小为sizeof(type)字节长,并将其所有位初始化为零。

. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0。

代码举例演示:

动态内存管理详解_第4张图片

 然后就是realloc函数:

. ptr 是要调整的内存地址
. size 调整之后新大小
. 返回值为调整之后的内存起始位置。
. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
realloc函数的功能是重新分配内存块;
我们一定会对内存的大小做灵活的调整。那 realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存, realloc 函数就可以做到对动态开辟内存大小的调整。
realloc函数在调整内存空间有下面两种情况:
1.原有空间之后有足够大的空间;
2.原有空间之后没有足够大的空间;
动态内存管理详解_第5张图片

情况 1:  当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2 : 当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

realloc使用代码演示:

#include
#include

int main() {
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	//初始化为1~10
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//增加一些空间
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL) {
		p = ptr;
		ptr = NULL;
	}
	else {
		perror("realloc");
		return 1;
	}
	//打印数据
	i = 0;
	for (i = 0; i < 20; i++) {
		printf("%d\n", *(p + i));
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

 动态内存管理详解_第6张图片

 动态内存管理详解_第7张图片

 代码运行结果:

动态内存管理详解_第8张图片

 下面来介绍一些常见的动态内存错误:

1.NULL指针的解引用操作

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}
2.对动态开辟空间的越界访问
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	//对动态开辟空间的越界访问
	for (i = 0; i < 20; i++)
	{
		p[i] = i;//越界访问
	}
	free(p);
	p = NULL;
	return 0;
}

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

int main()
{
	int a = 10;
	int* p = &a;
	printf("%d\n", *p);
	//
	free(p);
	p = NULL;

	return 0;
}

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

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

int main()
{
    int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用

	free(p);
	p = NULL;

	free(p);
	return 0;
//}

下面来进行一些题型的认识:

题型1:

void GetMemory(char *p)
{
 p = (char *)malloc(100);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str);
 strcpy(str, "hello world");
 printf(str);
}

解析:

.上面代码存在内存泄露
.上面代码可能会崩溃,即使GetMemory函数返回,str依然为NULL
.GetMemory函数无法把malloc开辟的100个字节带回来

题型2:

char *GetMemory(void)
{
 char p[] = "hello world";
 return p;
}
void Test(void)
{
 char *str = NULL;
 str = GetMemory();
 printf(str);
}

解析:

.GetMemory函数返回的地址无法正常使用,p[]只是函数里面临时创建的变量,出了作用域便会销毁,传回去的地址只会让str变为野指针

题型3:

void GetMemory(char **p, int num)
{
 *p = (char *)malloc(num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
}

解析:

此处忘记释放malloc所申请的空间,造成内存泄漏;

题型4:

void Test(void)
{
 char *str = (char *) malloc(100);
 strcpy(str, "hello");
 free(str);
 if(str != NULL)
 {
 strcpy(str, "world");
 printf(str);
 }
}

解析:

此处str提前释放了;导致str变为野指针;

还有很重要的一点,那就是C/C++程序的内存开辟:

动态内存管理详解_第9张图片

关于动态内存管理,还有最后一点,那就是柔性数组:

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

代码示例:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;

或者让更多编译器识别,也可以写成:

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;
柔性数组的特点:
. 结构中的柔性数组成员前面必须至少一个其他成员。
. sizeof 返回的这种结构大小不包括柔性数组的内存。
. 包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的    大小,以适应柔性数组的预期大小。
例如:
//code1
typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
柔性数组的使用:
//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
 p->a[i] = i;
}
free(p);
这样柔性数组成员 a ,相当于获得了 100 个整型元素的连续空间。
柔性数组的优势:
上述的 type_a 结构也可以设计为:
//代码2
typedef struct st_type
{
 int i;
 int *p_a;
}type_a;
type_a *p = malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
 p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

好啦,到现在动态内存管理的讲解就告一段落啦;

动态内存管理详解_第10张图片

你可能感兴趣的:(C语言,c语言)