C语言之动态内存管理

1.为什么存在动态内存分配?

我们掌握的内存开辟方式:

int val=20;         //在栈上开辟4个字节
char arr[10]={0};   //在栈上开辟10个字节的连续空间

但这种开辟方式空间大小固定,在数组声明的时候必须指定长度,它所需要的内存在编译时分配,这时就需要用到动态内存开辟。

2.动态内存函数:

>malloc和free:

malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

void *malloc(size_t size);     //调用实际生产空间比期望的大

---如果开辟成功,则返回一个指向开辟好空间的指针。

---如果开辟失败,则返回一个空指针,因此malloc的返回值一定要做检查。

---返回值的类型是void *,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

---如果参数size为0,malloc的行为是标准未定义,取决于编译器。

free,专门用来做动态内存的释放和回收:

void free(void *ptr);     //释放前后地址不变,只是把指针和空间的关联取消。此时的指针为野指针(悬垂指针)

---如果参数ptr指向的空间不是动态开辟,那free的行为是未定义。

---如果ptr的NULL指针,则函数什么事都不做。

---开辟空间如果不释放就会发生内存泄漏。内存泄漏随着程序的关闭而结束。

malloc和free都声明在头文件中。

例子:

int main(){
        //代码1
        int num=0;
	scanf("%d",&num);
	int arr[num]={0};
       
        //代码2
	int *ptr=NULL;
	ptr=(int *)malloc(num*sizeof(int));
	if(ptr){
		int i=0;
		for(;i<num;i++){
			*(ptr+i)=0;
		}
	} 
	free(ptr);   //动态释放内存
	ptr=NULL;   //释放后ptr再指向NULL,这一步很有必要
	return 0;
} 

>calloc:

void *calloc(size_t num,size_t size);

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

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

例:

int main(){
	int *p=calloc(10,sizeof(int));
	if(p){
		//使用空间 
	}
	free(p);
	p=NULL;
	return 0;
} 

>realloc:

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

---realloc让动态内存管理更灵活,可以做到动态内存大小的调整。

---ptr要调整的内存大小

---size为调整之后的新大小

---返回值为调整之后的内存其实位置

---这个函数调整原内存空间大小的基础上,还会将原来内存的数据移动到新的空间。

---realloc在调整内存空间大小的是存在两种情况:

(1)原有的空间后有足够大的空间。

要扩展内存就直接在原有内存后追加内存原来的数据不发生变化

(2)原有的空间后没有足够大的空间。

扩展方法:在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址。

例:

int main(){
	int *p = malloc(100);
	if (p)
	{
		//使用空间 
	}
	else
	{
		exit(EXIT_FAILURE);
	}
	//扩展内存
	//代码1
	p = realloc(p,1000);    //失败后将原来的空间为空,发生内存泄漏

	//代码2
	int ptr = NULL;
	ptr = realloc(p, 1000);
	if (ptr){
		p = ptr;
	}
	free(p);
	p = NULL;
	return 0;
}

2.常见的动态内存错误:

>对NULL指针的解引用操作

void test()
{
    int *p=(int *)malloc(INT_MAX/4);
    *p=20;   //如果p是NULL就会出问题
    free(p);
}

没有进行非空判断。

>对非动态开辟内存使用free释放:

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

free只用于动态内存开辟的释放。

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

void test()
{
    int *p=(int *)malloc(INT_MAX/4);
    p++;
    free(p);
}

动态内存必须整体开辟,整体释放。

>对同一块动态内存多次释放:

void test()
{
    int *p=(int *)malloc(INT_MAX/4);
    free(p);
    free(p);
}

动态申请的内存不能重复释放。

>动态开辟内存忘记释放:

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

忘记释放不在使用的动态开辟的空间会造成内存泄漏。

4.经典的面试题:

>

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

问题解析:

传值之后把str传给p,然后p指向动态开辟的空间,申请的空间与str无关,造成内存泄漏。

解决方案:

char  * GetMemory(char **p)
{
	*p = (char *)malloc(100);
	return *p;
}
void test(void)
{
	char *str = NULL;
	GetMemory(&str);
	strcpy(str, "Holle world");
	printf(str);
}
>
char  * GetMemory(void)
{
	char p[] = "holle world";
	return p;
}
void test(void)
{
	char *str = NULL;
	GetMemory(&str);
	printf(str);
}

运行test会出错。

因为函数定义的变量函数内有效,调用完后释放,这时调用GetMemory函数会产生栈帧,调完释放后,str还是指向p,然后调用printf函数也会产生栈帧,覆盖p所指向的栈帧。

>

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

会打印出holle world但是会出现内存泄漏。

这里有两点错误:没有判断是否申请成功和在用完后释放。

>

void test(void)
{
	char *str = (char *)malloc(100);
	strcpy(str, "Holle world");
	free(str);
	if (str)
	{
		strcpy(str, "Holle world");
		printf(str);
	}
}

free释放后指针会变成野指针,肯定不为空。

5.柔性数组

>在结构体中的最后一个元素是允许未知大小的数组,这就是[柔性数组]成员。

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

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

>柔性数组的特点:

---结构中的柔性数组成员前面必须至少一个其他成员。

---sizeof返回的这种结构体大小不包括柔性数组的成员。

---包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的大小应该大于结构体的大小,以适应柔性数组的预期大小。

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

>柔性数组的使用:

        int i= 0;
	type *p = (type *)mallloc(sizeof(type)+100*sizeof(int));
	p->i = 100;
	for (i = 0; i < 100;i++){
		p->a[i] = 1;
	}
	free(p);

这样柔性数组成员a,相当于获得了100个整形元素的连续空间。

这样做的好处是:

---方便内存的释放。

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

---有利于访问速度


猜你喜欢

转载自blog.csdn.net/qq_41889292/article/details/80517188