开启掘金成长之旅!这是我参与「掘金日新计划 12 月更文挑战」的第16天,点击查看活动详情


@TOC

前语(栈区、堆区、静态区)

==请耐心看完,看完后就会对内存中的空间区分有了更深刻的知道!==

咱们知道,任何一个变量的创建都会向内存请求空间用来存放,而在内存中的空间又区分为几个区域、最主要区分为:栈区、堆区、静态区

而咱们平常创建变量或者数组,如下:

int a=0;
int arr[1000];

这儿的a与arr都是在栈区拓荒空间的,栈区的特色之一便是出了效果域就会主动毁掉,所以它们的生命周期只需出了地点的效果域就完毕了。因而在栈区上拓荒空间的变量一般都是:局部变量、形参这种

而且咱们发现,在栈区上拓荒空间的一些变量,它们的巨细都是固定的,就比如上文的数组arr,它的巨细便是固定的4000字节,可是咱们能够想一下,有时分在运用它的时分,并不需要这么多的空间,或许仅仅只需要10个整形巨细的空间,而后边的990个整形空间都会被糟蹋掉,着实是惋惜呀!


那咱们不由美滋滋的会这么幻想,会不会存在咱们想用多少空间,就拓荒多少空间的或许呢?答案是有的! 咱们上面提到了内存中还区分有堆区,而堆区的特色之一便是:能够按自己的需求拓荒空间,而且该空间出了效果域不会主动毁掉,只能人工毁掉,这就实现了咱们想要的需求。

那么应如何在堆区拓荒空间呢?这儿就涉及到了以下讲到的几个函数:malloc、realloc、calloc,还有用来开释空间的free


或许有人还会疑问,上面的静态区是干嘛的,所谓的静态区,它的特色是:永久存在、生命周期一向到程序完毕所以在静态区拓荒空间的变量一般为:常量、const修饰的常变量、大局变量、以及static修饰的静态 大局/局部 变量。

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)


动态内存函数

咱们上面现已讲过了,动态内存分配是在堆区完成、而且空间是由程序员自己开释,因而切记,malloc、calloc、realloc与free都是成对出现的

malloc与free

首先是malloc,向内存请求size字节的空间,然后回来该空间的开始地址。

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)

运用演示

#include<stdio.h>
#include<stdlib.h>//头文件
int main()
{
	int* p = (int*)malloc(10*sizeof(int));
	//拓荒10个整形巨细的空间(40byte),然后用指针p来接纳该空间的开始地址
	//由于p是int*类型的,所以将该空间强制类型转换成(int*),保证用来接纳的指针类型与拓荒空间的类型共同
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//对空间进行一个判别,假设拓荒失利,打印过错,并回来。return 1表明非正常回来、
	//拓荒成功正常运用
	//...
	free(p);//运用完必定记住开释!(从哪里请求,从哪里开释,后边会将注意事项)
	p = NULL;//将指针置空
	return 0;
}

这儿必定要对p进行判别,由于假设空间拓荒失利,p便是一个空指针,后边假设对p进行操作与运用,很或许会出现很大的问题!

calloc与free

calloc与malloc很像,运用也基本相同,只不过它是这样运用的:拓荒num个巨细为size的空间,而且将空间的每个字节都初始化为0,而malloc拓荒的空间里边的值是随机值。

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)

运用演示

#include<stdio.h>
#include<stdlib.h>//头文件
int main()
{
	int* p = (int*)calloc(10,sizof(int));
	//拓荒个10个空间,每个空间巨细为一个整形巨细(总共40byte),然后用指针p来接纳该空间的开始地址
	//由于p是int*类型的,所以将该空间强制类型转换成(int*),保证用来接纳的指针类型与拓荒空间的类型共同
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//对空间进行一个判别,假设拓荒失利,打印过错,并回来。return 1表明非正常回来、
	//拓荒成功正常运用
	//...
	free(p);//运用完必定记住开释!(从哪里请求,从哪里开释,后边会将注意事项)
	p = NULL;//将指针置空
	return 0;
}

realloc与free

有时会咱们发现曩昔请求的空间太小了,有时分咱们又会觉得请求的空间过大了,那为了合理的时分内存,咱们必定会对内存的巨细做灵敏的调整。 realloc 函数就能够做到对动态拓荒内存巨细的调整

可是会存在原地扩容和异地扩容两种状况

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)
运用演示

#include<stdlib.h>//头文件
#include<stdio.h>
int main()
{
	int* p = (int*)malloc(40);//拓荒40byte
	//判别是否拓荒成功
	if (p == NULL)
	{
		perror("malloc fail");
		return 1;
	}
	//运用p指向的空间
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//0 1 2 3 4 5 6 7 8 9
	//扩容,用ptr接纳新空间开始地址
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL)
	{
		//扩容成功后,让p指向ptr,ptr置空
		p = ptr;
		ptr = NULL;
	}
		//运用
		for (int i = 0; i < 20; i++)
		{
		*(p + i) = i;
	}
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", *(p + i));
	}
	//运用完开释
	free(p);
	p = NULL;
	return 0;
}

常见的动态内存过错

咱们在运用动态内存分配时总是难免会犯一些不必要的过错,毕竟人非圣贤,孰能无过,接下来我将罗列这些常见的过错,以警示避免! 1、对空指针的解引证 例:

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

2、对动态拓荒空间的越界拜访 例:

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的时分越界拜访,error!
 }
 free(p);
}

3、对非动态拓荒内存运用free开释 例:

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//error!
}

4、运用free开释一块动态拓荒内存的一部分 例:

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的开始方位,error!
}

5、 对同一块动态内存屡次开释 例:

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

6、 动态拓荒内存忘掉开释(内存泄漏) 例:

void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
 //忘掉开释!error!
}
int main()
{
 test();
 while(1);
}

动态拓荒的内存空间不运用的时分必定要记住开释!


经典笔试题(再会张三)

接下来经过一些经典笔试题的解说来加深对动态内存分配的理解: 题目一:解释运行Test函数出现的成果

void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str, 100); strcpy(str, “hello”); printf(str); }

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)

分析:

在这儿,str首先置空,把str传曩昔,用指针p来接纳,然后p再指向新拓荒的空间,再把hello拷贝到该空间,接着打印。 听起来如同没什么毛病,可是咱们疏忽了以下几点!首先,malloc拓荒的空间并没有free,形成内存泄漏,这时最显着的过错! 然后,GetMemory这儿仅仅传址调用,也便是说,p的确指向了那块空间,可是实际上str并没有指向,这儿仅仅把str=NULL的值,传了曩昔,p=NULL,然后对p进行操作,咱们知道,传值调用,形参的改变不会影响实参!所以str仍是NULL,而strcpy一个空指针,就涉及到了对空指针的解引证,ERROR! 这两处过错最为丧命!

作为修正,咱们能够这样改正:

void GetMemory(char** p)//一级指针的地址用二级指针来接纳
{
	*p = (char*)malloc(100);//*p 等价于*&str,等价于str,即str=......
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);//传址调用,对形参的修正会影响实参
	strcpy(str, "hello");
	printf(str);
	free(str);//开释
	str = NULL;
}

笔试题二:以下代码运行成果:

#include<stdlib.h> #include<stdio.h> #include<string.h> char* Getmemory() { char p[] = “hello world!”; return p; } void test() { char* str = NULL; str = Getmemory(); printf(str); } int main() { test(); return 0; }

看起来没什么问题,str来接纳Getmemory回来的地址,然后打印,按理来说应该是hello world!,可是,实在成果却是一堆乱码

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)
这是为什么呢? 分析:

在前语那块,讲到了栈区的特色便是出效果域后会主动毁掉,咱们看这儿的p,p是数组名,表明数组首元素的地址,在这儿即字符’h‘的地址,然后回来该地址用str来接纳,可是!别忘掉p是个局部变量,局部变量在栈区拓荒空间,出效果域后会主动毁掉!也便是说 尽管传给了h地点的地址,可是当它传曩昔的那一刻,p地点的空间就主动毁掉,而str仍然记着那块空间,可是此时的那块空间现已不属于p了,这就形成了野指针的拜访,谁也不知道那块空间毁掉后里边是什么,所以打印乱码。

图解:

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)

张三 and 李四

举个张三与李四的故事来便利我们理解(),张三与李四是一对情侣,这一天,好不容易放假,李四就去酒店开了个房,然后把房间号告诉了李四,然后自己在房间里等他,等啊等,发现张三一向在打lol,还没有过来,就气的也没有告诉张三,就把房间退了,然后房间里现在住着王二麻子和他目标,假设张三再去找李四,的确,他记住房间号码,也能找到方位,可是他不知道房间里现已换人了,此时他不翻开房间还好,假设翻开的话……(狗头保命)

这儿的张三就比如题目里的str,p->酒店房间,p的地址->房间号码,空间毁掉之前里边住的是李四,毁掉后住的王二麻子。 str尽管能找到p之前指向的空间,但空间里的内容早已换了~

柔性数组

定义

柔性数组这个名词听起来很巨大上,但其实并没有什么特别的,那么它是什么呢?简单来说,便是结构体中的最终一位成员为数组,而且巨细未知。 举个栗子:

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员,也能够写成int a[0]
}type_a;

这儿的数组a,是结构体最终一位成员,而且巨细未知,所以它便是所谓的柔性数组.

特色 1、结构中的柔性数组成员前面有必要至少一个其他成员。 2、sizeof 回来的这种结构巨细不包括柔性数组的内存。 3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,而且分配的内存应该大于结构的巨细,以适应柔性数组的预期巨细。

运用

#include<stdlib.h>
#include<stdio.h>
struct S
{
	int i;
	int a[];
};
int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S) + 100 * sizeof(int));//为柔性数组a供给100个整形空间
	if (p == NULL)
	{
		perror("malloc fail");
		return 1;
	}
	p->i = 100;
	for (int j = 0; j < 100; j++)
	{
		p->a[j] = j;
	}
	free(p);//开释
	p = NULL;
	return 0;
}

【C/C++动态内存 or 柔性数组】——对动态内存分配以及柔性数组的概念进行详细解读(张三 or 李四)
这样有什么优点呢?我个人觉得,首先这个柔性数组它的空间能够依照自己的需要来拓荒,不会形成很多的空间糟蹋,还有便是便利开释,直接一次性free整个结构体指针即可。


end 日子本来沉闷,但跑起来就会有风!