开启掘金成长之旅!这是我参与「掘金日新计划 12 月更文挑战」的第16天,点击查看活动详情
@TOC
前语(栈区、堆区、静态区)
==请耐心看完,看完后就会对内存中的空间区分有了更深刻的知道!==
咱们知道,任何一个变量的创建都会向内存请求空间用来存放,而在内存中的空间又区分为几个区域、最主要区分为:栈区、堆区、静态区
而咱们平常创建变量或者数组,如下:
int a=0;
int arr[1000];
这儿的a与arr都是在栈区拓荒空间的,而栈区的特色之一便是出了效果域就会主动毁掉
,所以它们的生命周期只需出了地点的效果域就完毕了。因而在栈区上拓荒空间的变量一般都是:局部变量、形参
这种
而且咱们发现,在栈区上拓荒空间的一些变量,它们的巨细都是固定的
,就比如上文的数组arr,它的巨细便是固定的4000字节,可是咱们能够想一下,有时分在运用它的时分,并不需要这么多的空间,或许仅仅只需要10个整形巨细的空间,而后边的990个整形空间都会被糟蹋掉,着实是惋惜呀!
那咱们不由美滋滋的会这么幻想,会不会存在咱们想用多少空间,就拓荒多少空间的或许呢?答案是有的!
咱们上面提到了内存中还区分有堆区,而堆区的特色之一便是:能够按自己的需求拓荒空间,而且该空间出了效果域不会主动毁掉,只能人工毁掉
,这就实现了咱们想要的需求。
那么应如何在堆区拓荒空间呢?这儿就涉及到了以下讲到的几个函数:malloc、realloc、calloc,还有用来开释空间的free
或许有人还会疑问,上面的静态区是干嘛的,所谓的静态区,它的特色是:永久存在、生命周期一向到程序完毕所以在静态区拓荒空间的变量一般为:常量、const修饰的常变量、大局变量、以及static修饰的静态 大局/局部 变量。
动态内存函数
咱们上面现已讲过了,动态内存分配是在堆区完成、而且空间是由程序员自己开释,因而切记,malloc、calloc、realloc与free都是成对出现的
!
malloc与free
首先是malloc,向内存请求size字节的空间,然后回来该空间的开始地址。
运用演示
#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拓荒的空间里边的值是随机值。
运用演示
#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 函数就能够做到对动态拓荒内存巨细的调整
。
可是会存在原地扩容和异地扩容两种状况 运用演示
#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); }
分析:
在这儿,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!,可是,实在成果却是一堆乱码 这是为什么呢? 分析:
在前语那块,讲到了栈区的特色便是出效果域后会主动毁掉,咱们看这儿的p,p是数组名,表明数组首元素的地址,在这儿即字符’h‘的地址,然后回来该地址用str来接纳,可是!别忘掉p是个局部变量,局部变量在栈区拓荒空间,出效果域后会主动毁掉!也便是说 尽管传给了h地点的地址,可是当它传曩昔的那一刻,p地点的空间就主动毁掉,而str仍然记着那块空间,可是此时的那块空间现已不属于p了,这就形成了野指针的拜访,谁也不知道那块空间毁掉后里边是什么,所以打印乱码。
图解:
张三 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;
}
这样有什么优点呢?我个人觉得,首先这个柔性数组它的空间能够依照自己的需要来拓荒,不会形成很多的空间糟蹋,还有便是便利开释,直接一次性free整个结构体指针即可。
end 日子本来沉闷,但跑起来就会有风!