栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

动态内存管理(malloc,calloc,realloc)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

动态内存管理(malloc,calloc,realloc)

文章目录
  • 前言
  • 一、常用的动态内存函数
    • 1.malloc和free
    • 2.calloc
    • 3.realloc
  • 二、常见的动态内存错误
    • 1.利用realloc扩容时容易犯的错
    • 2.对空指针进行解引用操作
    • 3.对动态内存开辟的空间越界访问
    • 4.对非动态开辟的空间进行free
    • 5.使用free释放一块动态开辟内存的一部分
    • 6.对一块空间重复free
    • 7.动态开辟的空间忘记释放
  • 三.c/c++的内存分配
  • 四.经典面试题
  • 题目一
  • 题目二
  • 题目三
  • 题目四
  • 总结


前言

动态内存管理在c语言中是很重要的内容,它与数据结构紧密相关,所以我们需要对动态内存进行深入探究。

一、常用的动态内存函数 1.malloc和free

malloc的函数原型如下:

void* malloc (size_t size);

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

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

free的函数原型如下:

void free (void* ptr);

该函数用来释放动态内存开辟的空间:

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

下面这段代码便是malloc和free的使用方法:

int* ptr = NULL;
 ptr = (int*)malloc(num*sizeof(int));
 if(NULL != ptr)//判断ptr指针是否为空
 {
 int i = 0;
 for(i=0; i
 *(ptr+i) = 0;
 }
 }
 free(ptr);//释放ptr所指向的动态内存
 ptr = NULL;//是否有必要?
 return 0;
}
2.calloc

calloc的函数原型如下:

void* calloc (size_t num, size_t size);

这个函数功能和malloc一样,只是会把空间的每个字节初始化为0

3.realloc

realloc的函数原型如下:

void realloc(void* ptr , size_t size)

该函数的用法如下:

1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

当该函数开辟新的空间时:
如果原内存空间后面的空间足够,则会直接在后面追加一块空间;
如果原内存空间后面的空间不够,那么便会在堆上找一块空间,并返回这个新的地址

二、常见的动态内存错误

当我们了解了这几个基本函数后,我们便可以深入研究动态内存的知识。

1.利用realloc扩容时容易犯的错

请看如下代码有什么问题:

int main()
{
	int* p = (int*)malloc(20);
	if (p != NULL)
	{
		;
	}
	p = (int*)realloc(p, 40);//这一步有问题吗?
	if(p!=NULL)
		printf("%pn", p);
	free(p);
	p = NULL;
	return 0;
}

我们可以看到编译器给了如下警告:

如果我们realloc失败,那么会返回一个NULL指针,而这个空指针被赋值给p,那么原始内存块就会被泄露,我们无法再通过free来释放这块空间。

2.对空指针进行解引用操作

代码如下:

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

当malloc失败时,会返回一个NULL指针,而我们是不能对空指针进行解引用的。所以我们需要判断,当p不为空时才能进行后续操作。

3.对动态内存开辟的空间越界访问
void test()
{
 	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);
}
4.对非动态开辟的空间进行free
void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?
}
5.使用free释放一块动态开辟内存的一部分
void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}
6.对一块空间重复free
void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}
7.动态开辟的空间忘记释放
void test()
{
 	int *p = (int *)malloc(100);
 	if(NULL != p)
	 {
 		*p = 20;
	 }
}

int main()
{
	 test();
 	while(1);
}

我们不仅要在合适的地方释放空间,还要正确的释放空间。

三.c/c++的内存分配

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
四.经典面试题 题目一


p是一个形参,在调用GetMemory后,p被销毁,开辟的那块内存则泄露了。同时,str传进去时为null,而调用GetMemory时str未改变,所以还是null,程序崩溃。

当然,我们可以修改一下代码解决这两个问题。

我们需要str接收到开辟内存的那块地址,所以我们需要传str的地址进去,然后我们就可以利用这块空间,在用完之后释放即可。

题目二


该代码的运行结果是:

接下来我们分析一下原因:
GetMemory函数返回一个地址,但数组p是该函数的一个局部变量,调用结束后被销毁,str就会接受一个野指针,指向的空间位置未知,可能是原内容也可能被覆盖,所以打印的内容不确定。

题目三


该代码运行的结果是:

接下来我们分析一下这段代码:
该代码能打印hello是因为接收了开辟的那块空间的地址,但有一个问题,开辟的空间没有free,所以会造成内存泄漏。

题目四


该代码运行的结果是:

我们分析这段代码:str被free,它指向的那块空间被销毁,还给了操作系统,但str还是存着那块空间的地址,所以不为空,打印了world。str是一个野指针,造成了非法访问。

我们需要注意这个问题:当free一块空间时,还需要将指向这块空间的指针置为null,才能避免非法访问。

总结

动态内存管理是一个很重要的内容,其内存泄漏也是很容易忽视的问题,所以我们需要深入学习并养成良好,避免在写代码的过程中犯错。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1022478.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号