ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

动态内存管理(C语言)

2021-10-06 10:31:58  阅读:140  来源: 互联网

标签:malloc NULL 管理 int free C语言 动态内存 include


动态内存管理(C语言)

为什么存在动态内存分配

我们已经掌握了在内存开辟的方式

int n = 10;
int arr[10];

但是这样的开辟方式存在两个特点:
1.空间开辟大小是固定的
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
我们将内存划分为3个部分,分别为:栈区、堆区、静态区。栈区用来存放局部变量,函数形式参数,堆区用来存放动态内存,静态区用来存放全局变量,静态变量。

动态内存函数的介绍

malloc()函数——申请空间

形式:void *malloc( size_t size );
解释:malloc返回一个指向已分配空间的空指针,如果可用内存不足,则返回NULL。要返回指向void以外类型的指针,请对返回值使用类型强制转换。返回值指向的存储空间保证适当对齐以存储任何类型的对象。
例子:

#include<stdio.h>
int main()
{
	int* p = (int*)malloc(40);
	return 0;
}

malloc()函数是向内存中申请一块连续的空间,“40”表示申请的内存空间大小(单位:字节),申请完后mallco()函数会返回一个void类型的指针,因此,如果我们想用来存放整型,我们可以强制类型转化为int,并用int指针接收。
注意点:

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

free()函数——释放空间

形式:void free( void *ptr );
解释:free函数释放以前通过调用calloc、malloc或realloc分配的内存块(ptr)。释放的字节数等于分配块时请求的字节数(如果是realloc,则为重新分配)。
例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//释放空间
	free(p);
	p = NULL;
	return 0;
}

当malloc()函数开辟出空间时,我们对空间一系列操作后,当我们最后不在使用时,需要释放掉该空间,因此引入free()函数,我们将指针p作为参数传给free()函数,操作系统会将申请的40个字节的空间释放掉,但是此时的p指针仍然指向该内存,会造成野指针,因此我们需要将指针置为NULL。
注意点:

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

calloc——申请空间并初始化

形式:void* calloc (size_t num, size_t size);
解释:calloc返回一个指向已分配空间的指针。返回值指向的存储空间保证适当对齐以存储任何类型的对象。(这个函数是很malloc()函数及其的相似的,不同就在于calloc()函数在申请内存空间后同时会初始化为0。)
例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	return 0;
}

在这个例子中,calloc()函数就是申请10个字节的int类型的空间并且将所有的整型变为0。
注意点:

  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

realloc()函数——调整动态内存空间

形式:void* realloc (void* ptr, size_t size);
解释:realloc返回一个指向重新分配(可能已移动)内存块的空指针。如果大小为零且缓冲区参数不为NULL,或者如果没有足够的可用内存将块扩展到给定大小,则返回值为NULL。在第一种情况下,原始块被释放。在第二种情况下,原始块保持不变。返回值指向一个存储空间,该存储空间保证为存储任何类型的对象而适当对齐。
例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		printf("申请成功\n");
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	//释放空间
	//增加空间至20个int
	int* ptr = (int*)realloc(p, 20 * sizeof(int));
	free(p);
	p = NULL;
	return 0;
}

我们可以看到,我们先用calloc()函数申请10个整型的空间,在后续的过程中,我们发现空间不够时,我们需要拓宽空间,我们就可以使用realloc()函数进行拓宽,同样的,该函数也会返回首个类型的地址,我们也需要用指针接收该地址才能进行相关的操作。
注意点:

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

常见的动态内存的错误

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

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	*p = 0;
	return 0;
}

我们直接对p指针解引用是有风险的,原因就在于malloc()函数开辟空间失败,那么返回的指针就是空指针,这就会导致对空指针进行解引用,引起程序错误。
正确代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		*p = 0;
	}
	return 0;
}

我们在解引用时先进行判断,如果返回是NULL指针,那么就结束,如果不是那么可以进行后续的操作。

2、对动态空间的越界访问

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(200);
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 80; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 80; i++)
		{
			printf("%d ", *(p + i));
		}
		free(p);
		p = NULL;
	}
	return 0;
}

乍一看似乎没有错误,其实不然,我们创建200个字节的动态内存,也就是50个整型,但是我们用了80次循环,这就会导致越界行为,导致程序错误。所有我们在开辟动态内存后要关注是否会导致越界。
正确代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(200);
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 50; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 50; i++)
		{
			printf("%d ", *(p + i));
		}
		free(p);
		p = NULL;
	}
	return 0;
}

使用50次循环,也就用到了50个整型,200个字节,不会导致空间的越界访问。

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

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
	return 0;
}

我们在前面介绍free()函数介绍到,free()函数是用于动态内存开辟的释放,也就是说不是动态内存的空间是不能用free()函数释放的,因此会报错。

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

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*p++ = i;
		}
		free(p);
		p = NULL;
	}
	return 0;
}

我们知道当动态内存不在需要时,要释放还给操作系统,但是我们会释放一部分,上面的代码中*p++ = i已经改变了p,最后p指向的是“5”这个地址位置,因此free(p),只是释放掉“5”后面的位置,前面的并没有改变。因此程序错误。
正确代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*(p+i) = i;
		}
		free(p);
		p = NULL;
	}
	return 0;
}

这样写代码,并不会导致p发生变化,因此free(p)能够完全将动态内存够给释放掉。

5、对同一块动态空间多次释放

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		//....(代码)
		//....(代码)
		free(p);
		//....(代码)
		//....(代码)
		free(p);
	}
	return 0;
}

我们开辟一块动态内存空间使用后我们用free()函数释放掉,在进行操作后,有重复释放掉上一次的空间,会产生错误。
正确代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return -1;
	}
	else
	{
		//....
		//....
		free(p);
		p = NULL;
		//...
		//....
		free(p);
		p = NULL;
	}
	return 0;
}

我们在释放动态内存空间后,要将p指针置为NULL指针,我们上面说到过,free(NULL)什么事情都不干,因此不会报错,所以,我们要养成动态内存释放后将指针置为NULL的好习惯。

6、动态内存空间忘记释放

错误代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return -1;
	}
	return 0;
}

我们开辟了40个字节的动态内存,但是并没有释放,当程序没有结束时,这40个字节一直被占用,如果有其他的一直需要开辟动态内存空间,而不释放已经用过且不会再用的空间,就会导致内存越来越少,最后导致程序没有空间继续进行,程序崩溃。因此,当我们使用完动态内存,且不会在使用时,需要我们使用free()函数释放掉动态内存。

柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
举个例子:

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

a[ ]数组是结构体st_type中的最后一个成员,a[ ]数组并没有指定大小,因此a[ ]数组叫做柔性数组。

柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

对于第三点,举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;

int main()
{
	//包含柔性数组成员的结构体的使用,要配合malloc()函数等使用
	struct st_type* ps = (struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int));
	if (ps == NULL)
	{
		printf("%s\n", strerror(errno));
		return -1;
	}
	return 0;
}

根据第二点,我们知道此时的sizeof的大小为4,因此用传统的创建结构体变量会导致结构体大小为4,数组a[ ]就无法存放元素,所以柔性数组需要跟动态内存开辟结合起来,例子中,我们利用malloc()函数开辟动态空间,sizeof(struct st_type) 是结构体的大小,也就是4个字节,而10 * sizeof(int)是10个整型的大小,也就是10个整型数组元素的大小。这样创建我们就可以利用到了柔性数组的特点,后期也可以通过realloc()函数进行调整。

标签:malloc,NULL,管理,int,free,C语言,动态内存,include
来源: https://blog.csdn.net/qq_57563254/article/details/120547790

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有