C语言动态内存管理

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

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

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

但是上面的两种开辟空间的方式都有两个特点:

  • 空间开辟的大小是固定的。
  • 数组在声明的时候必须指定数组的长度,他所需的内存在编译的时候分配。

对于空间的需求,不仅仅上面的两种情况,如有时候我们需要的空间是在程序运行的时候才知道,不能满足我们的需求,所以这个时候只能试试动态内存的开辟了。

2 动态内存函数的介绍

2.1 malloc和free

void *malloc( size_t size );
这个函数向内存申请一块连续的可用的空间,并返回指向这个空间的指针。

  • 如果开辟成功,返回指向开辟好空间的指针
  • 如果开辟失败,返回一个NULL的指针,因此使用malloc函数的时候要判断指针是否为空
  • 返回值的类型是void * ,所以malloc函数并不知道开辟空间的类型,具体的类型是由使用者自己来决定的。
  • 如果参数size为0,malloc的行为是标准未定义的,取决于编译器。

光说不练假把式,下面我们通过代码来探索:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	//申请一块空间,用来存放10个整型
	int* p = (int*)malloc(10 * sizeof(int));
	//使用malloc开辟的空间需要进判断
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用:用开辟的空间,来存放0-9的数字。
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", p[i]);
	}
	printf("\n");
	
	//释放空间
	free(p);
	p=NULL;
	return 0;
}

现在这里开辟了空间,那是怎么样释放的呢?
malloc开辟的空间主要由两种释放方式:
1.free 释放------主动
2.程序退出后 ,malloc申请的空间会被操作系统回收------被动
正常情况下,谁申请的空间谁去释放,万一自己不释放,也要交代给别人,记得释放。

这里就该free函数登场了

void free( void *memblock );

用来释放动态开辟的内存。

  • 如果参数memblock指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数memblock是NULL指针,那么free函数什么事都不做。

使用方式如同上面代码。

这里可能就有人要问了,都使用free函数来释放动态开辟的空间了,为什么还要把它赋值为空。
是这样的free函数把开辟的动态空间释放了,归还于内存了,但是指针P还是指向开辟空间的起始地址,如果不小心使用了指针P,就会造成非法访问,形成野指针。

2.2 calloc

也是用来开辟内存空间的。
void* calloc (size_t num, size_t size);

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

在这里插入图片描述
可以看到变量0x00C0AD38的地址里面全部都初始化为0;

calloc函数和malloc函数有什么区别呢?这里只能说差不多。

calloc(10,sizeof(int ));
malloc(10*sizeof(int ));

上面都是开辟10个int类型的空间,只是开辟的参数由一点区别,前者是分开的,后者是合并的。除了是参数的区别,calloc函数申请好空间后或者使用完后,会将空间初始化为0,但是malloc函数不会初始化。

int main()
{
	//申请一块空间,用来存放10个整型
	int* p = (int*)calloc(10 , sizeof(int));
	//使用malloc开辟的空间需要进判断
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//使用:用开辟的空间,来存放0-9的数字。
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", p[i]);
	}
	printf("\n");

	return 0;
}

2.3 realloc

realloc 函数的出现,让动态内存的管理变得更加的灵活,因为有些时候我们会发现过去申请的空间太小了,有些时候又太大了,为了合理的管理 内存,我们就可以通过realloc函数来对开辟的内存大小进行灵活的调整,那realloc函数是如何做到的呢?我们先来看看realloc函数的定义 。

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

这里介绍一下这里的参数:

  • memblock 是要调整的内存地址
  • size 是调整之后新大小
  • 返回值是 调整之后的内存的起始地址
  • 这个函数会在原内存调整时候判断,如果后面的内存空间大小不够,就会寻找新的新地址,然后将原来内存中的数据移到新的空间,并返回新空间的起始地址。

realloc 函数在调整内存空间的时候分为两种情况:
1.原空间之后有足够大的空间。
这时如果想要扩展内存,就直接在原内存的后面直接追加空间,原空间的数据不发生变化。
2.原空间之后没有足够大的空间。
原空间后面没有足够大的空间,扩展的方法是:在堆空间上寻找新的合适大小连续空间来使用,释放旧的空间并返回新的内存地址。
在这里插入图片描述

来看realloc的具体使用,假设上面空间不够用,改为20个int空间:

	//空间不够,希望调整空间为 20个整型的地址
	int* ret = realloc(p, 20 * sizeof(int));
	//这里会有小伙伴问,为什么不用上面的P,这里要说的是:realloc开辟空间也会失败,
	// 失败返回的是NULL,并把原来的
	//数据为NULL,导致数据丢失,所以在使用的时候也要进行判断。
	if (ret != NULL)
	{
		p = ret;
	}
	return 0;
}

这里 会有同学问,realloc里面第一个参数传的是 NULL,怎么班呢?
这里就等价于:

int *p=(int *)realloc(NULL,40);// ==malloc(40)

realloc 函数也有malloc 的功能。

下面我们进行几个经典的笔试题

笔试题

题目一:

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int mian()
{
	test();
	return 0;
}

在这里插入图片描述
程序的运行如图所示:先创建了指针 str并把他赋值为NULL,接着调用GetMemory函数,创建临时变量P 然后对其开辟空间,函数GetMemory调用结束,但是P空间没有释放,接着使用strcpy函数,但是str还是空指针,没有接到p开辟的地址,接着对NULL进行解引用,程序崩溃。

题目二:

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

在这里插入图片描述
我们创建str指针,并赋值为NULL。调用GetMemory函数,在里面创建了字符数组P存储了hello world。这时候p指向的是h的地址,然后返回p的地址,数组P被销毁。str收到0xfff40的地址。这时候str是野指针,无法进行访问。

题目三:

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

内存泄漏。这里 能打印,但是没有释放开辟的内存,导致内存泄漏。

题目四:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

我们来看上面的这个代码:
创建str指针,并开辟100个字节的空间。然后调用strcpy函数把hello复制到str开辟的空间中,然后free释放开辟的空间,但是str任然指向开辟空间的起始地址,不为NULL。进行if判断,想要把world复制到str指向的空间。注意此时str是野指针,对野指针进行操作,非法访问内存。因为free函数 把开辟的空间返还给了操作系统。

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值