动态内存管理

目录

一.为什么要有动态内存分配

二. malloc和free

1.malloc

(1)代码举例:使用

(2)代码举例:malloc开辟失败

(3)代码举例:malloc开辟空间不放值,会是什么?

(4)知识点补充:栈区,堆区和静态区

2.free

三. calloc 和 realloc

1.calloc

2.realloc

(1)代码举例:calloc的使用

(2)代码举例:realloc申请空间 - malloc一样的功能

四. 常见的动态内存的错误

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

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

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

知识点补充:

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

5. 对同⼀块动态内存多次释放

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

五. 动态内存经典笔试题分析

题目1:

题目2:

 题目3:

题目4: 

六. 柔性数组

1.柔性数组的特点:

2.柔性数组的使用

(1)代码举例 - 使用柔性数组

(2)代码举例: - 不使用柔性数组

知识点补充:

七. 总结C / C++中程序内存区域划分


一.为什么要有动态内存分配

目前开辟内存有那些方式?
二种:
代码举例:

int main()
{
	//第一种:一次申请一小块空间,都是以一个变量申请
	int n = 0;//在栈空间一次申请4个字节
	char ch = 'a';//在栈空间一次申请1个字节

	//第二种:一次申请一块连续的空间可以放多个值 - 数组
	int arr[10] = { 0 };//一次向内存申请40个字节

	//缺陷和问题
	数组 - 存放一个班级的数学成绩
	int arr[30] = { 0 };//假设有一个班,存35个数学成绩就存不下

	return 0;
}

缺陷:这样申请的空间被固定死了,申请更大或更小的空间不行,除非把代码改了。
问题:当你开辟好的空间小了,大不了,开辟大了,小不了。

上述的开辟空间的⽅式有两个特点
• 空间开辟⼤⼩是固定的。
• 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整。
所以C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

二. malloc和free

1.malloc

函数原型:void* malloc(size_t size);   <stdlib.h>
作用:这个函数向内存申请⼀块/*连续可⽤*/的空间,并返回指向这块空间的指针。
• 如果开辟成功,则返回⼀个指向开辟好空间的指针。
• 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
• 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃己来决定。
• 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

参数解析:
void* malloc(size_t size);
参数:
size:表示你想申请多少个字节,也可以是表达式,交给计算机算。
返回类型:
void*:malloc只知道申请多大的空间,但是不知道会放什么类型的数据,所以malloc函数只能返回viod*。

(1)代码举例:使用

代码举例1:使用
int main()
{
	void* p = malloc(40);//p相当于指向了malloc开辟空间的起始位置
	//问题:
	//malloc返回void*指针,我们也要用viod*指针接收吗?
	//一但用了voud*指针接收就完蛋了,因为void*指针不能,(+-)和解引用
	//解决:
	//malloc开辟的空间虽然不知道回放什么类型数据,但作为程序员自己知道放什么数据
	//所以针对想放什么类型数据,强制转换就可以了

	int* p = (int*) malloc(0);//这是标准未定义行为,c语言都不知道怎么做,取决于编译器

	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");//perror - 会把对应的错误码的错误信息打印出来
		return 1;//异常返回
	}
	//开辟成功

	//使用 - 放10个整形的值 - 赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		/**p = i;
		p++; 这样写法p的地址走的太远了*/

		*(p + i) = i;
	}
	//打印 - 0 - 9
	for (i = 0; i < 10; i++)
	{
		printf("%d", *(p + i));
	}

	return 0;
}

我们使用malloc函数申请的空间,放的数据根据自己来决定,强制转换成对应的类型访问。

(2)代码举例:malloc开辟失败

int main()
{
	int* p = (int*)malloc(40000000000);

	if (p == NULL)
	{
		perror("malloc");//perror - 会把对应的错误码的错误信息打印出来
		return 1;//异常返回
	}
	//开辟失败成功

	return 0;
}

会显示:malloc:Not enough space 开辟失败 - 没有足够的空间。

(3)代码举例:malloc开辟空间不放值,会是什么?

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	int i = 0;
	
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//%d - 随机值
	//%x - cdcdcdcd - 关于函数栈帧创建与销毁内存里面随机值变量

	return 0;
}

总结:
1.使用malloc函数要判断有木有开辟成功。
2.malloc只负责申请空间,里面的值是随机的
3.申请了空间也要释放空间,用free。

(4)知识点补充:栈区,堆区和静态区

栈区:局部变量和函数的参数
堆区:malloc calloc realloc free
静态区:全局变量,静态变量

2.free

作用:专⻔是⽤来做动态内存的释放和回收的。
函数原型:
void free(void* ptr);
参数:任意类型的指针变量,你要释放那块空间,就传递那块空间起始位置给free。

free函数⽤来释放动态开辟的内存。
• 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
• 如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头⽂件中。

代码举例

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d", *(p + i));
	}

	//free 的使用 - 一但执行 p指向的空间已经还给操作系统
	free(p);
	1.危险:
	//p指向的空间已经还给了操作系统,但p还记得释放空间起始地址 - 很危险
	2.解决危险 - 设置为NULL,避免p成为野指针
	p = NULL;

	return 0;
}

思考free函数为啥不把 p 置成 NULL?
因为free没有能力把p设为NULL,因为参数是指针变量,如果想把外面参数设为NULL,因该传p的地址才对。

总结:
1.free操作只会把p指向的这块空间释放,但是p里面放的地址依然是释放空间的起始地址,所以很危险,一但使用就成为野指针。
2.释放完空间把p设置为NULL,避免成为野指针。

三. calloc 和 realloc

1.calloc

//跟malloc函数使用相似
原型如下:
void* calloc(size_t num, size_t size);
• 作用:函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

代码举例:calloc 的使用

int main()
{
	int* p = (int*)calloc(10,sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	int i = 0;
	//for (i = 0; i < 10; i++)
	//{
	//	*(p + i) = i;
	//}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));//10个全0
	}
	free(p);//ferr照常使用
	p = NULL;

	return 0;
}

malloc 和 calloc 区别:
1.可见的区别就是参数不一样,malloc传递一个参数算出整体大小传进去。calloc传递2个参数一个是多少元素和一个元素有多大。
2.malloc只负责开辟空间,不会对空间初始化。calloc在开辟好空间后并且把空间的每个字节初始化为0。

2.realloc

• realloc函数的出现让动态内存管理更加灵活。
• 有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的使用内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 /*realloc 函数就可以做到对动态开辟内存⼤⼩的调整。*/对malloc和calloc已经申请的空间变大变小。

原型如下:
void* realloc(void* ptr, size_t size);
参数:
ptr:一个指针指向的内存块,内存块是有malloc,1calloc和realloc提前申请好的空间。
size:你要申请内存块新的大小。
返回类型:
1.realloc调整空间失败,返回NULL。
2.调整空间成功呢,有2种请款:
(1)在已经开辟好的空间后边,/*没有足够的空间*/,直接进行空间的扩大。
在这种情况下,realloc函数会在内存的堆区重新找个空间(满足新的空间的大小需求),
同时会把旧的数据拷贝到新的空间,然后释放旧的空间,同时返回新的空间的起始地址。

(2)在已经开辟好的空间后边,/*有足够的空间*/,直接进行扩大。扩大空间后,直接返回旧的空间的起始地址。

(1)代码举例:calloc的使用

代码举例:
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));//10个全0
	}

	///空间不够,想要扩大空间,20个整形
	1.p = realloc(p, 20 * sizeof(int);
	问题:
	/*能不能用指针变量p来接收?
	不能,1.因为realloc一但内存开辟失败p会置为NULL。
	本来有40个字节,如果开辟失败,连着这40个字节都找不到了,不能使用。
	2.如果未来想释放这块空间也找不到了。*/

	解决问题:realloc的返回值,会单独拿一个临时变量指针接收,判断不为NULL,再交给p维护 
	int* ptr = (int*)realloc(p, 20 * sizeof(int));

	//判断 - ptr是否开辟成功 - 开辟成功在把ptr的值赋值给p
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		//ptr == NULL报错 - 开辟失败
		perror("realloc");
		return 1;
	}

	//开辟成功 - 使用

	//释放空间
	free(p);//ferr照常使用
	p = NULL;

	return 0;
}

第一种情况没有足够的空间,p和ptr地址不一样,说明是重新在堆区找的空间。

第二种情况有足够空间,p和ptr地址不样。说明是直接进行空间的扩大。

(2)代码举例:realloc申请空间 - malloc一样的功能

int main()
{
	int* p = (int*)realloc(NULL, 40);//等价于malloc

	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));//随机值
	}

	realloc是调整 - 第一参数传递空指针(NULL)就没有空间
	//第一个参数传非空指针 - 调整空间,可大可小
	//第一个参数传为空指针 - 等价于malloc,申请空间

	return 0;
}

总结:
1.malloc,calloc和realloc他们开辟空间都要判断是否开辟成功才使用。
2.只有calloc会初始空间为全0。
3.realloc该函数除了能够调整空间之外,还能实现和malloc一样的功能。

四. 常见的动态内存的错误

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

代码举例 - 对内存开辟函数函数返回值不做判断

错误写法:

int main()
{
	int* p = (int*)malloc(100);
	*p = 20;
	//1.不对malloc函数返回值做判断,编译器会报警告《取消对NULL指针p的引用》
	//p有可能是NULL

	return 0;
}

修改:

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		//报错误信息 - perror
		perror("malloc");
		return 1;
	}
	//判断完使用
	*p = 20;
	//使用完释放
	free(p);
	p = NULL;

	return 0;
}

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

代码举例 - 越界访问使用

错误写法:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当循环到第11次时候就越界访问了
	}
	free(p);
	p = NULL;

	return 0;
}

程序崩掉原因就是越界访问,HEAP - 堆空间出现问题。

修改 :- 不越界访问

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;//当循环到第11次时候就越界访问了
	}
	free(p);
	p = NULL;

	return 0;
}

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

代码举例 - 释放非堆区空间

错误写法:

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	//...
	p = &a;//p指向的空间就不再是堆区上的空间,是栈区
	free(p);
	p = NULL;

	return 0;
}

程序崩掉原因就是释放了非动态内存开辟的空间。

还存在一个很严重的问题:
p本来指向40个字节起始位置,一但指向a的地址,就不找到malloc开辟的40个字节空间,找不到就会存在内存泄漏的问题,想释放都不行。内存泄漏 - 申请走了,你又不用。

知识点补充:

malloc/calloc/realloc 申请的空间,/*如果不主动释放,出了作用域是不会销毁的。*/
释放的方式:
1.free主动释放
2.直到程序结束,才由操作系统回收。

代码举例:

int* test()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
		return NULL;
	else
		return p
	
}
int main()
{
	int* ret = test();
	return 0;
}

这个代码没有任何问题,这40个字节不会因为出了作用域而销毁。

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

代码举例

错误写法:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	p++;
	//释放
	free(p);
	p = NULL;

	return 0;
}

程序崩掉的原因就是释放空间的时候给的不是起始地址。

5. 对同⼀块动态内存多次释放

代码举例 - 对已经释放的堆区再次释放

错误写法:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	
	//释放
	free(p);
	free(p);

	p = NULL;

	return 0;
}

程序崩掉的原因就是对释放空间多次释放。

修改:每次释放完设为NULL

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用

	//释放
	free(p);
	p = NULL;
	free(p);
	p = NULL;

	return 0;
}

free参数传NULL什么事情都不干,所以第二次写法没啥意义。

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

代码举例 — 尤其是在公司开发这种现象是最讨厌的想象。

错误写法1:

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);//函数调用结束永远不退出
}

要是等待程序结束自动释放,就永远释放不了。

错误写法2:

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
		return;//问题:这里提前返回free执行不了。
	}
	free(p);
	p = NULL;
}
int main()
{
	test();
	while (1);
}

即使做到动态开辟函数要和free成对使用可能也会出错。

问题:
1.指针变量p是局部变量,函数调用结束p也销毁了,就再也没人记住malloc开辟的空间在哪里了。
2.申请走了,又忘记释放了,又找不到它了没机会释放了。
3.这种程序总是吃你内存,慢慢吃你服务器内存直到耗干机器崩掉。就要怀疑是不是内存泄漏。

总结:
1.最起码做到一点动态开辟函数要和free函数成对使用。
2.即使做到了也可能因为逻辑不清晰出现问题。

五. 动态内存经典笔试题分析

题目1:

void GetMemory(char* p)// p = NULL
{
	p = (char*)malloc(100);
	//p = malloc开辟空间的起始地址,并不影响str,str还是等于NULL
	//当函数调用结束,p创建的空间还给了操作系统,但是malloc开辟的空间找不到了 - 内存泄漏。
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);//这里是传值调用,形参是实参的临时拷贝
	strcpy(str, "hello world");//问题:str=NULL,对NULL指针解引用操作符程序崩溃
	printf(str);//printf的使用没问题
}
int main()
{
	Test();
	return 0;
}
请问运⾏Test 函数会有什么样的结果?//程序崩溃

问题:
1.GetMemory函数采用值传递的方式,无法将malloc开辟空间的地址,返回放在str中,调用结束后str依然是NULL指针。
2.strcpy中使用了str,就是对NULL指针解引用操作,非法访问,程序崩溃。
3.内存泄漏。

修改:目的是想要获取malloc开辟的这块空间地址,放在str里面去,strcpy拷贝在str指向的空间里面去
解决问题:
1.传值操作 - 传递指针变量str地址,所以形参要用二级指针保存一级指针的地址。
2.当对形参一次解引用访问的就是str这块空间。

代码举例1:传地址操作

void* GetMemory(char** p)
{
	*p = (char*)malloc(100);//改变形参,相当于改变实参
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);//str是一级指针,传递地址
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

代码举例2:直接返回malloc的地址

char* GetMemory()
{
	char* p = (char*)malloc(100);
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();//str是一级指针,传递地址
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

题目2:

char* GetMemory(void)
{
	char p[] = "hello world";//当函数调用完这个数组不存在,已经还给了操作系统没法使用
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();//函数调用完一但接收数组的起始地址,str就成野指针
	printf(str);
}
int main()
{
	Test();
	return 0;
}
请问运⾏Test 函数会有什么样的结果?//随机值

修改:用static修饰局部变量,出了作用域不销毁

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

典型返回栈空间地址的问题!栈上空间都是临时的,进入创建,出了销毁,一但返回栈空间的地址又被指针接收了就成野指针。

 题目3:

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	Test();
	return 0;
}
请问运⾏Test 函数会有什么样的结果?//打印hello,但是会存在内存泄漏的问题。

因为1.使用之前没有判断maalloc函数开辟的空间是否成功,2.也没有使用完之前释放,导致内存泄漏。

修改:用free释放

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

题目4: 

考察的是free之后不会手动设为NULL

代码举例:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	//str就是野指针
	if (str != NULL)
	{
		strcpy(str, "world");//非法访问
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}

请问运⾏Test 函数会有什么样的结果?看似程序打印了world,但已经形成了非法访问。

问题
1.释放放动态内存空间没有手动设为NULL,会成为野指针,因为已经还给你操作系统。
2.当你还要继续使用这块空间就形成非法访问。

六. 柔性数组

C99 中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
注意3点:
1.在结构体中
2.最后一个成员
3.最后一个成员是未知大小的数组 - 这个数组就是柔性数组!​​​

1.柔性数组的特点:

很重要:

• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
• sizeof 返回的这种结构⼤小不包括柔性数组的内存。
• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤小,以适应柔性数组的预期⼤小。

代码举例 - 测试柔性数组的大小

struct St
{
	char c;
	int n;
	int arr[0];//不知道大小
};

int main()
{
	printf("%d\n", sizeof(struct St));//8

	return 0;
}

总结:只计算柔性数组前面成员所占的空间。

2.柔性数组的使用

(1)代码举例 - 使用柔性数组

struct St
{
	char c;
	int n;
	int arr[0];
};
int main()
{
	//错误创建:
	//struct St s = {0};
	//1.首先s只有c和n的大小,并没有柔性数组的大小
	//2.这块空间是栈区创建的空间

	struct St* ps = (struct St*)malloc(sizeof(struct St) + 10 * sizeof(int));
	//创建了48个字节
	//为啥用结构体指针?柔性数组也属于结构体成员 - 为了访问柔性数组里面的成员。
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->c = 'w';
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//数组空间不够 - 用realloc调整,发现后面的空间可大可小,是有柔性变化的
	struct St* ptr = realloc(ps, sizeof(struct St) + 15 * sizeof(int));
	if (ptr != NULL)
	{
		ps = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	//...继续使用

	for (i = 10; i < 15; i++)
	{
		ps->arr[i] = i;
	}

	for (i = 10; i < 15; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n%d\n", ps->n);
	printf("%c\n", ps->c);

	//释放
	free(ps);
	ps = NULL;

	return 0;
}

特点:上述的空间都是在堆区上开辟的。

(2)代码举例: - 不使用柔性数组

struct St
{
	char c;
	int n;
	int* arr;
};
int main()
{
	//先开辟结构体的大小
	struct St* ps = (struct St*)malloc(sizeof(struct St));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	//最后开辟ps指向arr,arr指向int类型大小,当成数组使用
	ps->arr = (int*)malloc(10 * sizeof(int));
	if (ps->arr == NULL)
	{
		perror("malloc2");
		return 1;
	}

	//使用
	ps->c = 'w';
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	//数组空间不够
	int*ptr = (int*) realloc(ps->arr, 15 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}

	//使用
	for (i = 0; i < 15; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 15; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n%d\n", ps->n);
	printf("%c\n", ps->c);

	//释放 - 先释放谁很重要,先释放了ps就找不到了ps->arr里面的地址了
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;

	return 0;
}

特点:上述的空间都是在堆区上开辟的。

上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处:
第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤户。⽤户调⽤free可以释放结构体,但是⽤户并不知道这个结构体内的成员也需要free,所以你不能指望⽤户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给⽤、户⼀个结构体指针,⽤户做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度.
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你跑不了要⽤做偏移量的加法来寻址)

知识点补充:

内存碎片:多次malloc开辟的空间,会存在空间有缝隙,这些内存碎片利用不上,就浪费空间,利用率下降。
内存池:一次开辟一大块,里面的空间随便用,暂时不用,不用释放,下次还能接着使用,当你彻底不想用了,把内存池的空间整体释放。减少了大量内存开辟和释放,同时减少了内存碎片可能性。

补充:连续的空间在内存的访问是比较高的!不同空间访问效率低。
局部性原理:现在访问这个数据的时候,接下来大概率访问周围的数据。
1.空间局部性
2.时间局部性

解析:cup ----- 处理数据的时候,会去下面拿数据。cpu会根据局部性原理去拿数据
寄存器 - 访问速度最块
缓存
内存
硬盘

七. 总结C / C++中程序内存区域划分

看图分析:

总结
C / C++程序内存分配的⼏个区域:
1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内
存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数(形参)、返回数据、返回地址等。
2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。我们写的代码最终通过编译链接生成/*二进制指令*/,包括代码里面的常量值,常量字符串等等,都会放在代码段。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值