动态内存管理笔记

为什么存在动态内存管理?

  1. 空间在不同的需求下,可能需要不断调整,导致代码的可扩展性下降。
  2. 有可能因为空间划分不合理,可能会导致空间浪费的问题。
  3. 一般在栈上,能一次有效分配的空间是有限的。

基于以上原因,故需要动态内存管理,动态内存管理带来的好处有:

  1. 让我们在程序运行期间,来决定开辟空间的大小。申请多少就给多少。
  2. 有效使用空间。不会造成空间上的浪费问题。
  3. 可申请的空间更多。堆空间>栈空间。

动态内存管理是在堆区申请空间,申请的空间是连续的。
在栈上,栈:自动申请,自动释放。用户不需要进行动态内存管理。
堆空间:需要自己申请空间,自己释放:free。

 
 
 
malloc函数

原型:void * malloc(size_t size);

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

  • 如果开辟成功,返回一个指向这个地址的指针
  • 如果开辟失败,返回一个NULL指针,所以返回值一定要做检查
  • 返回值的类型为void*,可以按照我们的需求进行强转

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

原型:void free(void *ptr)

  • 如果ptr不是动态内存开辟的,那么free函数这样的操作是没有定义的
  • 如果ptr是NULL指针,函数就不会起作用

如果申请的空间未被free(忘了free):会造成内存泄漏。
内存泄漏会导致系统内存减小。如果程序退出了,内存泄漏的问题就自动恢复,内存归还给系统。

大部分时间都在运行的程序特别要注意内存泄漏的问题。

一个指针指向一段没有使用权限的空间,这样的指针叫做野指针(悬垂指针)。

int main()
{
	int num = 5;
	int *ptr = NULL;
	ptr = (int *)malloc(sizeof(int)*num);
	if (NULL == ptr)
	{
		exit(EXIT_FAILURE);
	}
	printf("%p\n", ptr);
	free(ptr);
	printf("%p\n", ptr);
	return 0;
}

在这里插入图片描述
在free之后,ptr依然指向该地址,但是该指针没有权限对该地址进行操作。

calloc函数

void *calloc(size_t num,size_t size);

  • 将num个大小为size的元素开辟一段空间,并将该空间每个节字初始化为0

与malloc函数相比:

malloc申请的空间,不会初始化。(可能快一些,因为少做了一些工作)
calloc申请的空间,会初始化。(按字节,初始化为0)

在申请空间的时候,一般所申请的实际大小是大于需要的内存空间大小的:
比如需要8个字节的空间,实际是大于8的。
为什么呢?

  1. 多出来的空间,不是给用户使用的,是给系统进行管理的。
  2. 将空间释放后,free不会对ptr置为空。释放的只是一个地址。但释放之后,不能使用,再使用会报错。释放的是指针和地址的对应关系。

 
 
动态内存管理的时候,要做到整体申请,整体释放。不要对指针指向进行更改。

realloc函数

void *realloc(void *ptr,size_t size)

  • ptr是将要调整的内存地址
  • size是调整的大小
  • 返回值为调整之后的起始地址

如何调整?
realloc函数调整存在两种情况:

  1. 扩展:

直接扩展:原空间足够大,可直接进行扩展,返回值一样
重新开辟:(原空间不足),重新开辟空间,返回新地址
在这里插入图片描述

int main()
{
	int num = 5;
	int *ptr = NULL;
	ptr = (int *)malloc(sizeof(int)*num);
	if (NULL == ptr)
	{
		exit(EXIT_FAILURE);
	}
	printf("%p\n", ptr);
	int *ret = realloc(ptr, 10000);
	if (NULL != ret)
	{
		ptr = (int *)ret;
	}
	printf("%p\n", ptr);
	free(ptr);
	return 0;
}

在这里插入图片描述

2.收缩:直接进行收缩即可。

在使用realloc函数的时候,不能这样使用:直接讲重新申请的空间赋给旧的指针。
这样做会导致,如果开辟失败,返回会是NULL,原空间就会被置为NULL,这段空间就再也找不到了。指针也找不到了。堆空间就会丢失,导致内存泄漏。可用判定,来决定是否这样做。就如上述代码。

realloc函数执行之后,使用的是新申请的空间,包括free也是。而不再使用旧的空间,free也不释放旧的空间。

 
 
 

一个比较有意思的题:

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

GetMemory函数返回p的起始地址,调完的时候,就释放了。但是,p数组的地址,其数据仍然是存在的。
这就和计算机存储文件是一样的,文件删除,删除了这一块的使用权限,文件无效,但其数据仍然是存在的,数据恢复就是基于这样的性质进行的恢复的。
最后调用printf函数的时候,形成了新的栈帧结构,就会覆盖这个数据。故而这个代码输出的就是乱码。

 
 
 
 
在动态内存管理这块,要多注意:

  1. 申请的空间必须进行校验。(是否开辟成功)
  2. 使用完毕,一定要进行释放。(否则会造成内存泄漏)
  3. 不要越界访问。
  4. free释放的一定是动态内存开辟的空间,栈上的不可以。
  5. 不能将起始位置进行更改,也不能进行局部释放。
  6. 不要多次进行释放(第一次释放完之后,指针就对该空间无操作权限了,再释放就会出错)。

猜你喜欢

转载自blog.csdn.net/w903414/article/details/106943162