c语言动态内存管理

前言:

在c和c++中都会大量使用动态内存管理,使用c和c++实现数据结构的时候,也会使用动态内存管理

我们在写代码的时候,一般会这样向内存申请空间

比如:

int main()
{
	int i = 10;  //这里向内存申请4个字节的空间
	int arr[10] = { 0 }; // 这里内存申请10个字节的连续空间
	char c = 'c'; // 这里向内存申请1个字节的空间
	return 0;
}

但是我们发现,上面的这种写法有两个特点:

1. 空间开辟的大小是固定的

2. 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小就不能调整

 但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,那数组在编译时开辟空间的方式就不能满足了。

这时c语言就提供了动态内存开辟函数,让程序员可以自己申请空间释放空间,这样就增加了灵活性。

malloc()函数

malloc()函数的声明

动态内存开辟函数

头文件 <stdlib.h>

声明:void* malloc(size_t size);

· ptr 指向开辟好的空间的起始地址

· size 要开辟多少个字节

1. 如果malloc函数开辟成功,返回一个指向开辟好了的空间的地址

2. 如果malloc函数开辟失败,返回一个空指针

所以使用malloc函数的返回值一定要做检查

3. 如果size是0,根据编译器,malloc函数可能返回一个不指向任何地址,也可能什么也不做

作用:这个函数向内存申请一块连续可用的空间(相当于数组),并返回这个指向这个空间的地址

 怎么使用呢?

举例

int main()
{
	//这里malloc是void*类型的指针,需要强转成自己需要的类型的指针
	int* ptr = (int*)malloc(20);//这里向内存申请20个字节的空间
	//这里判断malloc是否开辟成功
	if (ptr == NULL)
	{
	   //开辟失败
		perror("malloc");//perror 打印malloc函数的错误信息
		return 1;//提前返回
	}
	//开辟成功,使用空间
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(ptr+i) = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ",*(ptr + i));
	}
	return 0;
}

 malloc()函数申请的空间在内存中的存放

我们知道内存中有栈区,堆区,和静态区,代码块等等;

局部变量和形式参数是放在内存中的栈区的,malloc申请的空间是放在内存中的堆区的。

free()函数

在前面,我们向内存申请了空间,那我们不想要这个空间了,我们应该怎么把这个块空间还给操作系统了,这时候我们就要用到free()函数了,这个函数是专门用来释放动态内存空间。

free()函数的声明

动态内存释放函数

头文件<stdlib.h>

声明:void free(void* ptr);

ptr:指向的是要释放的动态内存开辟的空间的起始地址

1.如果参数ptr指向的空间不是动态内存开辟的,那么free函数什么也不做

2.如果参数ptr是NULL指针,free函数什么也不做

3.ptr指向的必须是动态内存开辟空间的起始地址,如果在别的地址,free没办法帮你释放了

作用:释放动态内存开辟的空间。

 怎么使用呢?

举例:

int main()
{
	//这里malloc是void*类型的指针,需要强转成自己需要的类型的指针
	int* ptr = (int*)malloc(20);//这里向内存申请20个字节的空间
	//这里判断malloc是否开辟成功
	if (ptr == NULL)
	{
	   //开辟失败
		perror("malloc");//perror 打印malloc函数的错误信息
		return 1;//提前返回
	}
	//开辟成功,使用空间
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(ptr+i) = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ",*(ptr + i));
	}
	//不想使用了,还给操作系统
	free(ptr);
	//这里有个小知识,free完后的ptr还是指向这块空间,但是这个空间没有使用权限了,这时ptr就是野指针了
	//我们需要把ptr指向NULL;
	ptr = NULL;
	return 0;
}

calloc()函数

calloc函数其实和malloc函数一样,也是动态内存开辟函数

calloc()函数声明

动态内存开辟函数

头文件:<stdlib.h>

声明:void* calloc(size_t num,size_t,size);

num: 要分配的个数

作用:为num个大小为size的元素开辟一块空间,并且会把空间的每个字节都初始化为0。

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

 怎么使用呢?

举例

int main()
{
	//向内存申请5个整型的空间
	int* ptr = (int*)calloc(5,sizeof(int));
	//判断
	if (ptr == NULL)
	{
		perror("calloc");
		return 1;
	}
	//开辟成功

	return 0;
}

calloc函数与malloc函数的区别

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

而malloc开辟的空间是未初始化的随机数。

malloc函数没有初始化,所以在速度上面会比calloc函数快一点。

malloc函数

malloc函数空间内容未初始化

当然我们也可以打印出来看一下

calloc函数 

calloc函数空间内容初始化

 也可以打印出来看一下

 结论:如果我们要对申请的空间的内容要求初始化,我们就要calloc函数,反之用malloc函数

realloc()函数

如果我们对动态内存申请的空间太小了,或者太大了,怎么进行调整呢,这时候,就用到了我们的realloc函数了,realloc函数是对动态内存申请的空间进行大小的调整的

realloc()函数的声明

realloc函数是用来对动态内存申请的空间进行大小调整的

头文件<stdlib.h>

声明:void* realloc(void* ptr,size_t size)

ptr: 指向要调整大小的动态内存申请的空间的起始地址

size: 要调整的大小

返回值:返回调整后空间的起始地址

这个函数在调整原来的空间大小的基础上,还会把原空间里面的数据移动到新空间

 怎么使用呢?

举例

int main()
{
	//向内存申请20个字节大小的空间
	int* ptr = (int*)malloc(20);
	//判断
	if (ptr == NULL)
	{
		perror("malloc");
		return 1;
	}
	//申请成功,使用
	//使用中...
	//发现空间大小不够,需要进行调整
	int* p = (int*)realloc(ptr, 40);//这里向空间增加到40个字节
	//这里我们为什么不直接用ptr来直接接收调整后空间的地址,因为如果空间开辟失败,返回一个空指针给ptr接收,那么ptr里面原来的数据就没了
	// 所以我们选择用同类型的指针接收调整后空间的地址
	//判断
	if (p == NULL)
	{
		perror(realloc);
		return 0;
	}//申请成功,可以使用40个字节的空间
	ptr = p;//用ptr再接收调整后空间的地址
	//这里p的作用已经结束了,我们把他释放掉
	free(p);
	p = NULL;
	//使用完...释放
	free(ptr);
	ptr = NULL;
	return 0;
}

realloc函数在调整空间时会的情况

realloc函数在调整空间时会出现三种情况

情况1:原有空间之后有足够大的空间

情况2:原有空间之后没有足够大的空间

 情况3:调整空间失败

返回空指针。

小知识:

其实realloc函数可以完成和malloc一样的功能

int main()
{
	realloc(NULL, 20);// == malloc(20)
	return 0;
}

 常见的动态内存的错误

1.对NULL指针的解应用

int main()
{
	int* p = (int*)malloc(INT_MAX*10);
	*p = 20;//如果p的值是NULL,就会有问题
	return 0;
}

 这里没有对malloc进行判断,如果malloc开辟失败,返回一个空指针,然后我们空指针进行了解引用。

2.对动态内存空间越界访问

int main()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

这里就for循环里面的结束条件写错了,当i  = 10 的时候就越界访问了

3.对非动态开辟内存使用free释放

这里我们通过调试看到编译器报警告了

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

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

这里p++了,就不再指向动态内存的起始位置

5.对同一块动态开辟内存多次free

int main()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
	return 0;
}

多次使用free

 这里好的方法就是,在free之后直接将它置为空指针

6.动态内存开辟后忘记释放(导致内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 20;
	}
}
//这里都是局部变量,出这个函数之后就销毁了
int main()
{
	test();
  //那么这里就找不到动态开辟的空间了
  //现在想释放都释放不了
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
假如你这个程序要7*24小时的不间断的运行,就比如说游戏服务器,如果你这个程序有内存泄漏的问题,那么这个程序就在一直申请空间,但是不还给操作系统,那么总会有内存被使用完的时候,那么这个程序就崩溃了。
小知识:程序在关闭的时候,操作系统也会自己回收的。

 动态内存的题目

题目一

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

运行这个代码会出现什么现象?

 解析

1.内存泄漏,申请了空间没有释放

2.因为这个指针p是在函数内部创建的,出函数了之后这个p指针变量销毁了

3.那么这里的str还是空指针,strcpy对空指针拷贝数据,就会出错

 题目二

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

这个代码运行会出现什么现象?

 解析

这里虽然我们返回了p数组首元素的地址,但是p数组是在函数里面创建的,出这个函数之后就被销毁了,这个函数返回的就是一个野指针,我们对野指针进行访问就会出问题。

返回栈空间地址问题

 题目三


void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
int main()
{
	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指向的空间还给了操作系统,但是free不会把str置为空,进入了if语句,然后strpy对已经还给操作系统的空间进行拷贝,就导致了非法访问。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值