动态内存管理

目录

1.什么是动态内存管理?

2.动态内存函数与用法。

3.常见的动态内存错误。

4.柔性数组。


1.什么是动态内存管理。

 在我们开辟空间时基本上都是通过int、char、double、int[]数组、等类型来帮助我向内存申请空间,这种方法开辟出来的空间都是固定的,有些时候我们没办法知道用户所需要的空间的大小,开辟多了浪费了内存空间,开辟少了又不够用,所以这时我们就应该使用动态内存开辟的方法解决这个问题。

2.动态内存函数与使用方法。

2.1malloc与free

C语言提供的动态内存开辟的函数:

Void * malloc (size_t大小);分配内存块分配一个大小为字节的内存块,返回一个指向块开头的指针。新分配的内存块的内容没有初始化,剩下的是不确定的值。如果size为0,返回值取决于特定的库实现(它可能是空指针,也可能不是空指针),但是返回的指针不能解引用。一个参数大小内存块的大小,以字节为单位。Size_t是一个无符号整型。返回值成功时,指向该函数分配的内存块的指针。此指针的类型始终为void*,可以强制转换为所需的数据指针类型,以便解除引用。如果函数未能分配所请求的内存块,则返回一个空指针。

也提供了一个free函数用来释放动态开辟的内存。

 以前通过调用malloc、calloc或realloc分配的内存块被释放,使其再次可用以进行进一步的分配。如果ptr没有指向分配给上述函数的内存块,它将导致未定义的行为。如果ptr是空指针,则该函数不执行任何操作。注意,这个函数不会改变ptr本身的值,因此它仍然指向相同的(现在是无效的)位置。参数断续器指向先前使用malloc、calloc或realloc分配的内存块的指针。

malloc与free都在头文件stdlib中。

说了这么多那么这两个函数应该怎么使用呢,举个例子:

int main()
{
	int* prt = (int*)malloc(40);//malloc向内存申请了40个字节的空间;
	if (prt == NULL)
	{
		printf("%s", strerror(errno));//如果保错的话打印报错信息并异常返回
		return 1;
	}
	int i = 0;
	int* num = prt;
	for (i = 0; i < 10; i++)//打印出来看看
	{
		*(prt + i) = i;
		printf("%d ", num[i]);
	}
	free(prt);//将申请的内存释放掉
	prt = NULL;//将这个指针置为空以防野指针问题;
	return 0;
}

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

 这样就成功向内存申请空间啦。

calloc函数

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下

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

realloc函数

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

原型如下:

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

realloc函数又分为两种情况:

情况1:原有空间后有足够的空间

情况2:原有空间后没有足够的空间

动态内存管理_第2张图片情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些。

3.常见的动态内存错误

3.1对同一块内存多次释放

int main()
{
	int* prt = (int*)malloc(40);//malloc向内存申请了40个字节的空间;
	if (prt == NULL)
	{
		printf("%s", strerror(errno));//如果保错的话打印报错信息并异常返回
		return 1;
	}
	int i = 0;
	free(prt);//将申请的内存释放掉
	free(prt);//将申请的内存释放掉
	free(prt);//将申请的内存释放掉

3.2对NULL指针的解引用操作

int main()
{
	int* ptr = (int*)malloc(40);

	*ptr = 30;//这里没有判断ptr指针是否为NULL,如果为空的话会出现问题。
	return 0;
}

3.3对动态内存越界访问

void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
printf("%s",strerror(errno));
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}

3.4对非动态开辟的内存ferr

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

3.5使用free释放动态内存开辟的一部分

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

3.6动态内存开辟忘记释放

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

当我们忘记了将动态内存开辟的空间释放时。如果程序结束,动态申请的内存由操作系统自动回收,如果程序不结束则会造成内存泄漏问题。

4.柔性数组

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

比如:

struct S{
   int a;
   char arr[];//柔性数组
};


4.1 柔性数组的特点:


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

typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

4.2 柔性数组的使用

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

这样就相当于柔性数组获得了100个整形元素的连续空间;

4.3柔性数组的优势

第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给
用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你
不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好
了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。


第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正
你跑不了要用做偏移量的加法来寻址
 

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