ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

详解【动态内存管理】

2021-11-29 20:02:22  阅读:209  来源: 互联网

标签:malloc 动态内存 管理 int free 详解 内存 str NULL


目录


秃头侠们好呀,今天来聊聊动态内存管理

本章重点

  • 为什么存在动态内存分配
  • 动态内存函数的介绍
    malloc
    calloc
    realloc
    free
  • 常见的动态内存错误
  • 柔性数组

为什么存在动态内存分配

我们以往学过的内存开辟无非是

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

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

1、空间开辟的大小是固定的
2、数组在声明时必须指定数组长度,它所需要的内存在编译的时候分配

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

动态内存函数的介绍

malloc / free

malloc

void* malloc (size_t size);

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

  • 如果开辟成功,返回一个指向开辟好空间的指针
  • 如果开辟失败,返回一个NULL,所以malloc的返回值要做检查,看是否开辟成功
  • 返回值是void* 所以malloc开辟的空间不知道类型,由开辟者自己决定
  • 如果size为0,则malloc行为的标准是未定义的,取决于编译器

free

void free (void* ptr);

C语言提供了另外一个函数free,专门是用来做动态内存的释放回收

  • free用来释放动态开辟的内存
  • 如果ptr指向的空间不是动态开辟的,则free的行为是未定义的
  • 如果ptr是NULL指针,则啥也不做
  • 如果动态内存不释放会造成内存泄漏(后面具体说)
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	
	//开辟失败
	if (p == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	//开辟成功
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", p[i]);
	}
	printf("\n");
	free(p);
	p = NULL;

	return 0;
}

1、如果你开辟的空间过大有可能会开辟失败,所以必须检查。
2、开辟成功了,因为是连续的空间,所以p相当于一个数组。
3、最后记得释放动态开辟的空间,你拿的就要还回去,防止内存泄漏。

4、最后要把该指针置空NULL,有什么必要?

指针被free后,指针指向的还是原来的区域,但是这片区域已经不归自己使用了,这片区域可以被别人用,被别人覆盖了,所以你已经变成野指针了,如果你不置为空,你去访问这个地方了,就造成非法访问了,会有不安全因素。且可以防止对一个已经释放的指针多次释放,造成程序崩溃,但我们可以对NULL指针多次释放。

举个例子:
比如你有一个女朋友,有一天你和她分手啦,这里相当于free,她已经不属于你了,你们之间已经没有关系了,她现在可以成为别人的女友了,但是你脑子里还记者她,还记着她的联系方式,这里相当于p指针还指向原来的内容,这样对她是不好的,因为你还能根据联系方式去打扰她的生活,置为NULL,就是清除你对她的记忆,喝下忘情水。(当然祝大家都幸福哦)

calloc

calloc

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

在这里插入图片描述
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc

realloc

void* realloc (void* ptr, size_t size);
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整
  • ptr是要调整的内存地址
  • size是调整之后新大小
  • 返回值为调整之后的内存起始位置
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
  • realloc在调整内存空间的时候有两种情况

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

要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

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

原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,前面的数据会拷贝下来,这样函数返回的是一个新的内存地址,之前的realloc会自己free掉

常见的动态内存错误

1、对NULL指针的解引用操作

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

所以使用动态内存分配,需要判断是否开辟成功,如果成功再使用,否则不能使用,返回NULL,但是不能对NULL解引用

2、对动态开辟空间的越界访问

int*p=(int*)malloc(200);
for(int i=0;i<80;i++)
{
//....
}

总共申请了200÷4=50个元素,而你的for循环的判断条件到80了,所以当大于50的时候,会出现越界访问。

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

void test()
{
int a = 10;
int *p = &a;
free(p);//可以吗?
}

当然是不可以的显然是不可以的,因为a是在栈上开辟的空间,不是堆上
free只能释放堆上动态开辟的空间!

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

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//可以吗?
}

当然是不可以的,因为自增后,p指向的位置改变了,而free释放必须是释放全部的动态开辟的空间(起始位置),不能是部分。

5、对同一块动态内存多次释放

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

不可以对一个同一块动态内存重复释放,但是可以这样

free(p);
p=NULL;
free(p);
p=NULL;

6、动态开辟内存忘记释放(内存泄漏)
首先在堆上申请的空间有两种回收方式
1、free
2、程序退出时,申请空间自动回收

如果不对开辟的空间进行释放,则会造成内存泄漏,你的电脑就会越来越卡
所以当使用完动态开辟的内存,一定要记得释放

看几个笔试题

题一:

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

该程序结果是什么?为什么会有这样的结果?

结果是:程序崩溃,什么也不打印
原因
str传给p的时,是值传递,p是str的临时拷贝,当malloc开辟空间的起始位置放到p中时,str并没有改变还是NULL;
当str是NULL,strcpy要把hello world拷贝到str指向的空间时,因为str是NULL,所以不知道拷到哪里,程序崩溃。

那我们应该怎么更改呢?
很简单,我们应该传str地址,这样*p就是str
在这里插入图片描述题二:

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

结果是啥?
在这里插入图片描述为啥是随机值?

这里需要一点函数栈帧的知识
函数在栈上开辟栈帧,函数返回,销毁栈帧,当栈帧销毁后,里面的东西都还给操作系统了,return p,这里的返回值p是通过寄存器(eax)传回来的,把p的地址通过寄存器赋给str,虽然str拿到了p的地址,但是栈帧已经销毁,p地址指向的空间已经不属于p了,这时候p其实已经算是野指针了,如果你再去访问这个空间,就造成了非法访问了。如果空间内容没有被覆盖,还有可能打印出来,如果被别人使用了,就打印随机值了。

题目三:

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

这个代码结果是什么?有什么问题?
在这里插入图片描述看似打印出来了,其实已经出现了问题

char* str = (char*)malloc(100);
strcpy(str, "hello");
这两句没有问题,str指向malloc出来的空间,strcpy把hello拷贝到这片空间
free(str);
if (str != NULL)
{
strcpy(str, “world”);
printf(str);
}
str被free,则malloc出来的空间还给操作系统,不属于自己了,但是str指向的地址没有变,只是变成野指针了,if判断进去,strcpy(str, “world”);这里就出现问题了,因为这片空间已经不属于自己了,你又使用了,所以造成非法访问了,虽然最后打印出来了了,但是早已出错了。
我们应该在free完之后就应该把str置空,养成好习惯,才不容易出错

C/C++程序的内存开辟

在这里插入图片描述
C/C++程序内存分配的几个区域:

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

现在我们是否就理解了static关键字修饰局部变量的例子了,为啥生命周期会改变

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁 所以生命周期变长

柔性数组

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员。
例如:

struct S
{
int i;
int a[0];//柔性数组成员
};

有些编译器会报错,无法编译可以改成下面

struct S
{
int i;
int a[];//柔性数组成员
};

柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少有一个其他成员
  • sizeof 返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
struct S
{
int i;
int a[];//柔性数组成员
};
printf("%d\n",sizeof(struct S));//结果是4

柔性数组的使用

//代码1
struct S
{
int i;
int a[];
};
int i = 0;
struct S*ps= (struct S*)malloc(sizeof(struct S)+100*sizeof(int));

p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
p=NULL;

这样柔性数组成员a,相当于获得了100个整型元素的连续空间

柔性数组的优势

//代码2
struct S
{
int i;
int *a;
};
struct S*p = (struct S*)malloc(sizeof(struct S));
p->i = 100;
p->a = (int *)malloc(100*sizeof(int));

for(i=0; i<100; i++)
{
p->a[i] = i;
}

free(p->a);
p->a = NULL;
free(p);
p = NULL;

上述 代码1 和 代码2 可以完成同样的功能,但是谁更好呢?代码一更好!
原因:

1、方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了两次内存分配,并把整个结构体返回给用户。用户free一次可以释放结构体,但是他不知道结构体里成员也是分配出来的,也需要free释放,我们不能指望用户自己发现这个事。如果我们用代码一这种,结构体内存和成员内存一起只分配一次,返回给用户一个指针,那么用户free一次就可以释放所有内存。
2、有利于访问效率
连续的内存有利于提高访问速度,且减少内存碎片(这点效率没有提升很高,都要用偏移量的加法来寻址)


这期就到这里啦,感谢阅读,我们下期再见
如有错 欢迎提出一起交流
关注周周汪

关注三连么么么哒

标签:malloc,动态内存,管理,int,free,详解,内存,str,NULL
来源: https://blog.csdn.net/m0_57018588/article/details/121610296

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有