动态内存管理(malloc,free,callc,realloc)以及柔性数组

一、为什么会有动态内存分配

1.1向内存申请空间的两种形式:

        在栈空间上申请空间(第一种:空间大小不可变):

----特点:申请时必须指明长度,一旦申请成功,空间大小不能改变。

	int a = 0;            //向栈空间申请4个字节大小空间
 
	char b = 'a';         //向栈空间申请1个字节空间
 
	char arr[10] ={ 0 }; //向栈空间连续申请10个char类型的大小空间(10个字节)
 
	int s[10] = { 0 };    //向栈空间连续申请10个int类型大小的空间(40个字节)

        在堆区上申请空间(第二种:空间大小可以改变):

 二、动态内存开辟的函数:malloc

函数原型:void* malloc(size_t size);

        1.函数返回类型是void*(指针类型);void*可以指向任何类型的指针;但是void类型指针不是进行解引用,必须进行强制类型转换才能进行解引用;

        2.size是在堆区申请的空间大小,单位是字节;

例如:int *p=(int*)malloc(sizeof(int));

指在堆空间申请大小为4个字节的空间,并把首地址强制类型转换成整形(int)指针,然后赋给整形指针变量p;

        3.头文件:

#include<stdlib.h>

        注意事项:

1.申请(开辟)成功:会返回指向开辟空间的指针;

2.申请(开辟)失败:会返回一个NULL类型的指针,所以一定要对malloc的返回值进行检查;

3.返回值的类型为void*,所以malloc函数只管开辟空间,但是不知道空间的类型;具体在使用的时候由使用者决定;

4.当size为0时;malloc的行为是未被定义的,大小取决于编译器,可能申请成功,可能申请失败;

三、free函数

         c语言中,提供一个一个函数free,用来释放动态内存空间,即申请了动态内存空间,就要去释放动态内存空间。

        函数原型:void free(void * str);

void*函数可以接受任意类型的地址,比如:int*,char*。

注意事项:

        1.如果str指向的内存不是动态的,free的行为是未被定义的。

        2.如果str是NULL指针,则free什么事情都不做。

        3.malloc和free的声明都包含在头文件#include<stdlib.h>中。

        4.free函数只会对内存进行释放,开始的指针任然还在,最后要将他变为NULL;(p=NULL)

让我们看看下面代码:

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main()
{
	int* p = (int*)malloc(10* sizeof(int)); //申请一块很大的动态空间
	if (p == NULL)       //检查是否开辟成功,开辟失败,p为NULL
	{
		perror("malloc");
		return 1;       //非正常退出
	}
	printf("%p\n", p);
	free(p);
	printf("%p\n", p);
	p = NULL;
	printf("%p\n", p);
	return 0;
}

5.str是要释放内存的起始地址,下面看一段代码,判断是否正确? 

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

         for循环中,p每次++,最后对p进行内存释放,是不正确的,因为此时的p不指向最上面开辟的动态内存的起始地址。

四、calloc函数

          函数原型:void* calloc(size_t    num,size_t   size);

声明包含于#include<stdlib.h>

• 函数的功能是为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

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

•如果以后要对字节初始化为0,那么就使用calloc,不用初始化,就使用malloc。

五、realloc函数

          函数原型:void* realloc(void* str,size_t  size);

• realloc函数的出现让动态内存管理更加灵活,可以调整malloc和calloc动态空间的大小。

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(3, 4);   //申请3个大小为4个字节的空间
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	for (int i = 0; i < 3; i++)
	{
		p[i] = i + 1;
	}
	               //1 2 3 
	int* str=(int*)realloc(p, 4 * sizeof(int));
	if (str != NULL)         //对申请的地址进行检查
	{
		p=str;
		*(p+3) = 4;     //1 2 3 4 	
		for (int i = 0; i < 4; i++)
		{
			printf("%d ", *(p + i));
		}
		free(p);
		p = NULL;
	}
	return 0;
}

 realloc函数调整空间的两种情况:

        1.如果开始创建的空间后面有足够的空间,那就在后面新增空间。

        2.如果后面空间不够,那就在堆区新找一块新的空间,返回新的起始地址。并且会把开始的数据拷贝到新的空间里,把原来的空间自动释放。

        3.如果上面两种都不行,则申请失败,返回NULL。

所以,我们使用realloc函数时,如果直接把地址传给原来的p指针,如果申请失败,那么就找不到原来的内存,这时候就会发生内存泄漏。

因此我们应该先用新创建一个新的指针,如果指针不是NULL,则再赋给p。

六、常见的动态内存错误

1.对NULL进行解引用操作。

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

         没有对p指针进行检查,就对p进行解引用操作,因为如果申请失败,p可能为NULL。

解决办法:用if进行判断,或者用assert(p)进行判断。

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

        3.对非动态开辟的空间进行free释放。

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int a = 0;
	int* pa = &a;
	free(pa);   //对非动态内存进行释放
	pa = NULL;
	return 0;
}

4.使用free释放动态内存的一部分。 

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	for (int i = 0; i < 3; i++)
	{
		*p = i + 1;
		p++;
	}
	free(p);
	p = NULL;
	return 0;
}

         free(p)没用把malloc申请的动态内存空间全部释放。for循环中,p向后移了3位。不再是动态内存的起始位置。

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

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	free(p);
	free(p);   //对动态内存内存进行多次释放
	return 0;
}

        解决办法:如果我们可以再第一次free(p)后面把p变为NULL。那么后面的free就什么都不做。 

        6.动态内存空间忘记释放(内存释放)。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
void test()
{
	int* p = (int*)malloc(3 * sizeof(int));
	if(p==NULL)
	{
		assert(p);
	}
	for (int i = 0; i < 3; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
}
int mian()
{
	test();
	return 0;
}

        该代码中,p创建的是局部变量,当test函数运行结束时,p就会销毁,但是在test()函数中,没有进行动态内存释放。如果我们再想在main函数中对动态内存释放,我们是不能找到这一块空间的,这个时候就会放生内存泄漏,如果内存释放过多,内存就会被耗干。

*如果发生内存释放,如果有内存泄漏,当程序结束的时候,操作系统会进行回收。

*解决办法:malloc函数与free函数成对出现。

        malloc函数成对出现也不一没有内存泄漏,因为可能虽然成对出现,但是有一些语句没有执行,被一些语句提前返回了。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
int mian()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	*p = 0;
	if (*p == 0)
	{
		return 0;
	}
	free(p);
	p = NULL;
	return 0;
}

 七、柔性数组

        定义:结构体中最后一个元素允许时位置大小的数组 

 注意:

        1.柔性数组前必须有其他成员!!!

        2.sizeof(结构体)的结果,不包含柔性数组的大小

        3.柔性数组一般用malloc开辟空间时,(struct S*)malloc(sizeof(struct S) + 40);

40为柔性数组的大小。

        4.malloc开辟空间时

实例:

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

struct S
{
	int n;
	int arr[0];//柔性数组
};

int main()
{
	//printf("%d\n", sizeof(struct S));
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i + 1;
	}

	//空间不够,需要增容
	struct S* ptr = realloc(ps, sizeof(struct S) + 60);
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	ps = ptr;
	ps->n = 15;
	for (i = 0; i < 15; i++)
	{
		printf("%d\n", ps->arr[i]);
	}

	//释放
	free(ps);
	ps = NULL;

	return 0;
}
  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值