超级好懂的——(浅谈)动态内存管理+动态内存函数+错误案例分析+经典笔试题

一、为什么要有动态内存管理

话说为什么要动态内存管理?
**我们已经的内存开辟方式有:**
int val=20;//在栈上开辟四个字节
char arr[10]={
    
    0}//在栈上开辟十个字节的连续空间

上述的开辟空间的方式有两个特点:

●空间开辟的大小是固定的。
●数组在申请的时候,必须指定数组的长度,它所需要的内存在编译时分配。


因为我们在实际应用的时候,在栈上开辟的空间时候,
必须明确所开辟的大小(即在程序运行前),
而用户本身有所需要的空间,导致先前在栈上开辟的空间或大或小,
不能完全匹配用户所需,导致我们写的程序能解决的问题面比较窄,
不能解决通用的问题。而且在栈上开辟的空间不会很大,
而在堆上可以开辟很大的空间,在栈上申请的空间一般定义的都
是临时的变量,而在堆上开辟的空间必须要由程序员管理(程序员申请,程序员释放)

二、什么是动态内存函数

C语言提供了一个动态开辟的函数

  void* malloc(size_t size)
 这个函数向内存申请了一块连续可用的空间,并返回指向
 这块空间的指针
 ●如果开辟成功,则返回一个指向开辟好空间的指针。
 ●如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
 ●返回值得类型void* ,所以malloc函数并不知道开辟空间的类型,
 具体在使用的时候使用者自己来决定。
 
 C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
   void free(void* ptr)

free函数用来释放动态开辟的内存。
  ●如果参数ptr指向的空间不是动态开辟的,那free
  函数的行为是未定义的。
  ●如果参数ptr是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h头文件中。

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

    void* calloc(size_t num,size_t size);

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


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

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

●ptr 是要调整的内存地址
● size 调整之后的新大小
● 返回值为调整之后的内存起始位置。
●这个函数调整原内存空间大小的基础上,还会将原来内存
中的数据移动到新的空间。
●realloc 在调整内存空间的是存在两种情况:
 情况1:原有空间之后又足够大的空间
 情况2:原有空间之后没有足够大的空间。

三、错误案例分析


1、对NULL指针的解引用操作
void text()
{
    
    
	int *p = (int *)malloc(INT_MAX / 4);
	*p = 20;//如果堆空间申请失败,就会出问题,所以先要进行合法判定
	
	free(p);

}


2、对非动态开辟内存使用free释放
void text()
{
    
    
	int a = 10;
	int *p = &a;
	free(p);//ok?
}


3、使用free释放一块动态开辟内存的一部分
堆空间是整体申请,整体释放的,不能整体申请,局部释放

void text()
{
    
    

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


4、对堆空间重复释放
void text()
{
    
    
	int *p = (int *)malloc(100);
	free(p);
	free(p);
}


5、动态开辟内存忘记释放(内存泄漏)

void text()
{
    
    
	int *p = (int *)malloc(100);
	if (NULL != p)
	{
    
    
		*p = 20;
	}
	//缺少free(p);
	//动态开辟的空间一定要释放,而且要正确释放
}

int main()
{
    
    
	text();
	while (1);//程序会一直存在
}

由于text()是一个函数,调用函数就会形成栈帧结构
函数中在堆上申请空间,申请空间的地址就由指针p保存(即p指向堆空间的起始地址)
p是函数内部的指针变量(即是在栈上开辟的变量)
函数调用完之后,函数内部的所有临时变量就会被释放(即p就会被释放,p的指向就不存在了)
而申请的堆空间仍然存在(因为必须要有程序员申请,程序员释放)


6、对开辟的空间的越界访问
void text()
{
    
    
	int i = 0;
	int *p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
    
    
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
    
    
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);

}

四、几道经典笔试题


**第一题:**
调用GetMemory函数,形成栈帧
void GetMemory(char *p)  //形参实例化(发生值拷贝,p和str没有任何关系,即对p修改,不影响str),在栈上开辟一个新的空间
//p是该函数的局部变量
{
    
    

	p = (char*)malloc(100);
	//在堆上申请了100个字节的空间,将空间的起始地址存放到p内;
	
	
	//首先申请之后没有对指针p没有进行判空(因为可能会申请失败)
}

int main()
{
    
    

	char *str = NULL;
	GetMemory(str);
	//函数返回,栈帧结构释放,临时变量p被释放,
	//起始地址没了,就找不到申请的堆的空间,造成内存泄漏。
	
	strcpy(str, "hello world"); //由于str指向为空,导致拷贝也会出错
	printf(str);
	//程序最后没有释放申请的堆空间,同样也会造成内存泄漏
	
	return 0;
}

```//bash

在这里插入图片描述

总结:
● 返回值没有判空
● 使用完毕没有free
● 在使用期间一定会发生内存泄漏
● p空间与str空间不是同一份空间,对p进行修改,
并不修改str,GetMemory函数无法把堆空间通过str带出来,此时拷贝就会出错,导致程序崩溃

上述代码不能将堆空间带出来,若想实现该功能,则要用到二级指针

void GetMemory(char **p)
{
    
    

	*p = (char*)malloc(100);
}

int main()
{
    
    
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);

	return 0;
}
或直接用带返回值的函数返回
char *GetMemory(int num)
{
    
    
	char *temp = (char*)malloc(num);
	return temp;
}

int main()
{
    
    
	char *str = GetMemory(num);
	return 0;
}




**第二题:**
问题:程序打印出来的乱码是在GetMemory函数返回的
时候就已经乱了呢?还是怎么着?
大部分人都会认为:
首先p是个数组,且是一个临时数组
一旦函数结束,p数组就会被释放
所以str指向的空间就是废弃空间,打印str就是乱码

而实际上:
我们应该知道一点就是,电脑上对数据的拷贝是真的进行
数据拷贝,而删除,只要设置数据无效即可


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

void Test(void)
{
    
    
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

在这里插入图片描述

调用GetMemory函数形成栈帧结构,产生了p数组,
函数返回,栈帧结构被释放,但栈帧结构里面的数据仍然在,并不会被清空,只是被设置为了无效数据,
str仍然指向p数组的内容,因为你要打印str,
而printf是一个函数,在执行打印逻辑之前(即在调用打印之前,要先调用printf函数),要给printf函数形成栈帧(由于GetMemory函数释放,所以是基于Test()函数向下形成新的栈帧结构,且printf函数中也有临时变量)
栈帧结构一旦形成就会覆盖GetMemory函数的数据
所以再进行打印,str指向的内容已经是被修改过的内容,所以打印的是乱码。

*所以乱码不是由于GetMemory函数调用返回导致的
而是调用printf后数据被改写所导致的*


**注意**
printf也是函数,调用则也会形成栈帧结构,里面的临时变量会覆盖
旧的数据。


**第三题:**

void Text(void)
{
    
    
	char *str = (char *)malloc(100);
	//首先没有判空,不知申请有没有成功
	strcpy(str, "hello"); //所以拷贝可能会出现问题。
	free(str);
	if (str != NULL)
	{
    
    
		strcpy(str, "world");//一旦free(str),若拷贝一定会出错
		printf(str);
	}
}

猜你喜欢

转载自blog.csdn.net/cfk17829572643/article/details/110183105