1.3.4 内存申请与释放
1.3.4.1 变量与空间
1.3.4.1.1 宏观上
- 多个程序同时运行是怎么个情况
- 所有程序/软件(程序=软件一个面对用户一个面对程序员)的运行,是由操作系统统一调配的。操作系统是程序的运行环境。
- 运行中的多个程序之间,内存是不交叉的
- 程序结束,操作系统还要释放其使用的资源
- 不释放会咋样?资源会被一直占用,别的程序无法使用了。
1.3.4.1.2 微观上
a、程序里定义的变量,申请的空间
申请在哪块内存区域?
由谁申请?
什么时候释放?
由谁释放?
b、什么是栈(堆栈就是栈)区?
内存由五部分组成:为啥分这么多区域?这跟公司差不多,部门分的细致,工作分发的就有针对性,效率就会高。
- 栈区
特点:
内存由系统申请,在变量生命周期结束时由系统释放。
生命周期:定义到释放。不是所有的变量在程序结束时(软件关闭时)才释放,服务器例子。
- 堆区
特点:
由我们随时申请,由我们自己随时释放比如:int等
- 全局区
- 字符常量区
- 代码区
1.3.4.2 内存分配与释放
1.3.4.2.1 malloc分配函数介绍
a、malloc函数前述
- 概念:
malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。
- 功能
在堆区申请指定大小的连续的 一段空间,并返回该空间的首地址。
这个函数的操作类似于数组。
- 举例子
int *p = (int*)malloc (4);//malloc先申请一个4字节的空间,可以是float或其他类型,(返回的是一个首地址值),所以用int*标志(也称强制类型转换)然后装到p里面。即:申请的这个空间是一个int型的空间,一次访问4个字节。
或者
int *p ;
p = (int*)malloc (4);
- 函数参数
就是要申请的字节数
malloc( )里面是一个整型表达式:5 2*2 sizeof(int)*n 等
size_t:
32编译器环境unsigned int sizeof size_t 4字节
64编译器环境long unsigned int sizeof size_t 8字节
- 头文件
stdlib.h
malloc.h
b、malloc函数使用
- 对三种情况进行讨论
malloc(正常):内存碎片,这个就是正常申请空间
malloc(0):这个一般不会做,因为申请0空间没有意义,但是会返回一个地址,0表示该地址起多少个空间可用
malloc(极限):这个是申请空间过大,超过系统内存大小!申请失败会返回 一个地址,NULL 也就是0,相当于没有空间!没申请到,这个一般是内存不够用了。NULL 就是申请空间的一个返回值,NULL=0.
注意:所以一般,在申请空间这里,使用指针前加个判断,并作一些提示。
if (NULL==p)//也就是判断指针地址是否是0,申请是0的时候就是失败
{
printf(“申请空间失败”);
}
- 强制类型转换:
形式:(类型*)//VS()可以不写,最好写上,移植性高
概念:malloc就是返回的void* ,所以可以转换成我们想要的类型,操作。就是申请一个int类型空间,但是可以转换成其他类型空间。
- *P的使用
类比:
int a;
int *p1 = &a;
*p1 == a;
对于:int *p2 =(int*) malloc(4); //malloc申请回来一个地址,相当于&a,malloc的空间没有名字
*p2 == 这个空间 //相当于 a
- 内存空间赋值
方式一、内存空间全部赋值0
int *p = (int*)malloc (40);//申请40个字节,相当于10个元素。这个操作和数组一样!!!
int i;
for(i=0;i<10;i++)
{
p[i]=0;//赋值每个元素为0
}
for(i=0;i<10;i++)
{
printf(“%p”,p[i]);//输出结果都是0
}
方式二、内存空间赋值函数memset(效果和上面一样)
这是内存赋值,按字节来赋值,一个字节一个赋值!!!(1字节=8位)
头文件:memory.h
memset(p,0,40);//效果和上面一样,输出是0
memset(p,1,40);//这个输出就不是1了,因为是按字节赋值,赋值1就是四个字节即:1111(4字节),换成8位,1111就是00000001 00000001 00000001 00000001八位,按十进制:16843009
- 注意点:
1、千万别越界:比如你要申请四字节当作int使用,千万别申请三个
不会像数组报错,malloc不会报错!
2、一个指针指向了一块堆区空间,千万不要再指向另一块
会导致内存丢失,(因为已经申请过了,但是指针名字被覆盖了)造成内存泄漏,也就是一块空间没有用,丢失了。
当用一个新的指针记录这个地址后,原来的指针就可以解放。(因为有名字了,可以释放)
1.3.4.2.2 free释放函数介绍
b)free函数使用
- 形式:free(p);//这样就完事了没有返回值
- 参数p:p就是malloc申请的返回值,要释放的空间的指针
- 释放:释放只是释放堆中的数据,但是栈中仍然存在指针变量
例子:
int *p = (int*)malloc(4);
*p = 12;
printf("%p %d\n",p ,*p);// 00E58FC8 12
free(p);
p=NULL;//指针复位
printf("%p %d\n", p, *p); //00E58FC8 17891602
- 注意点:
在free之后,p地址在,但是数据不在额
所以在实际项目中,一般指针释放后,赋值一个NULL; 一个指针赋值NULL了,就叫空指针=0=NULL(指针复位),不赋值0语法没错,只是存在隐患。
所指向的空间访问受限,叫野指针,这个东西不能直接使用不能操作。
不能重复释放同一块空间//free(p); free(p);
不能释放栈区空间//比如释放int a这个应该是操作系统自己释放,不能人为
一定要释放头指针//释放从前到后(一个空间:头地址+字节数),必须是这个空间的头地址
- 崩溃情况:
malloc运行中的崩溃,一定是某一行具体的代码引起的,大家一定要找到。
运行结束的崩溃,基本就是内存越界操作了。非常难找,所以大家一定要小心,细心的使用指针,
1.3.4.3 基本数据类型空间的申请与使用
int *p = (int*)malloc(4);
short *p1 = (short*)malloc(sizeof(short));
float *p2 = (float*)malloc(4);
double *p3 = (double*)malloc (8);
long *p4 = (long*)malloc(sizeof(long));
1.3.4.4 数组与malloc关系
1.3.4.4.1 申请一维数组和malloc
特点
- 栈区数组:
一段连续的空间,数组名字是首元素的首地址,类型不变,定义什么就是什么!
- 堆区数组
那我们malloc一段空间(这时是没有类型的,即:可以任意定义类型),然后记住首地址,malloc回的空间时灵活的,类型可以变。
定义
- 形式:
int* p =(int*)malloc(sizeof(int)*5);// 4*5这个不能初始化
p就装着首地址,p+1就是第二个元素,......
这个不能初始化(可以用memset赋值成0或者用循环赋值任意值)
- 访问:
*(p+0)==*p *(p+1) *(p+2) *(p+3) *(p+4)
公式 *(p+n) = p[n]
- malloc的数组与int a[3]这种栈区数组的区别:
定义有点儿不一样,使用一模一样, malloc需要free
1、malloc的数组可任意指定长度
int a;
int* p;
scanf("%d", &a); //可以在运行过程中指定任意大小
p =(int*)malloc(a);//动态数组,动态分配空间
2、int a[2];就不一样了,个数在定义时候确定,以后就不能修改了
1.3.4.4.2一维数组指针接这个空间
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int main(void)
{
int i;
int(*p)[5] = (int(*)[5])malloc(sizeof(int) * 5);//不加p:(*)[ ]就表示数组指针的类型,加p就是指针的变量
//利用栈区数组对比学习:
(int(*)[5])malloc(sizeof(int)* 5); 这意义和 &a一样就是数组的地址
//int a[5];
//int (*p1)[5] = &a;//数组的指针
*(*p + 0) = 1;//*p==a 就是首元素的地址
*(*p + 1) =2;// *p+1=a+1就是加一个元素的地址 所以加*(*p+1)
*(*p + 2) = 3;
*(*p + 3) = 4;
*(*p + 4) = 5;
//记住三句话:
//1、数组名字是数组的首元素的首地址
//2、*(p+n)==p[n]
//3、一个指针指向一个变量或者空间 *这个指针就是这个变量或者空间
for (i = 0; i < 5; i++)
{
printf("%d\n",(*p)[i]);
}
free(p );//这个和malloc是一套的
system("pause");
return 0;
}
1.3.4.4.3二维数组指针接这个空间
int (*p)[2][3] = (int (*)[2][3])malloc(2*3*4);
类比栈区二位数组:int a[2][3];
int (*p)[2][3] = &a;
访问:*(*(*p+1) + 2)== (*p)[1][2] *(*p+2) 套用公式:*(p+n)==p[n]
//二维数组:
int i, j;
int(*p)[1][2] = (int(*)[1][2])malloc(sizeof(int)* 2*3);//(*)[]表示数组指针的类型
//类比二维数组:(int(*)[5])malloc(sizeof(int)* 5); 这意义和 &a一样就是数组的地址
//int a[2][3];
//int (*p1)[2][3] = &a;二维数组的指针
(*p)[0][0] = 1;//*p==a 就是首元素的地址
(*p)[0][1] = 2;// *p+1=a+1就是加一个元素
(*p)[0][2] = 3; //一定加()/对于*p[][]表示p先与*结合表示指针类型,不然p与[][]结合就是先表示为数组
(*p)[1][0] = 4;
(*p)[1][1] = 5;
(*p)[1][2] = 6;
//记住三句话:
//1、数组名字是数组的首元素的首地址
//2、*(p+n)==p[n]
//3、一个指针指向一个变量或者空间 *这个指针就是这个变量或者空间
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d\n", (*p)[i][j]);
}
}
free(p);//这个和malloc是一套的*/
1.3.4.5 calloc realloc
1.3.4.5.1 calloc
功能:申请一段空间数组
形式举例:int* p = (int*)calloc(5, 4); //五个元素,一个元素占四字节
头文件:<stdlib.h> and <malloc.h>
特点:每个元素会被初始化成0
1.3.4.5.2 realloc
功能:重新分配内存的大小(在原来的空间后面连续操作),原来8字节,重新分配成100字节或者5字节
如果该内存地址起有足够的内存,那么内存首地址p1 == p;
如果该内存地址起没有足够的内存,那么内存首地址p1 != p; 即p1有新的地址了,前面所有的数据复制进新空间
参数1是内存块的起始地址,参数二是重新分配的大小空间值。
int *p1;//定义一个指针变量
int* p = (int*)malloc(20);//五个元素,一个元素占四字节
size_t a = _msize(p);//_msize()就是输出malloc申请空间的大小
printf("%d",a );
p1 =(int*) realloc(p, 1000);//重新给malloc分配空间大小,这个地址p1和p一样,只不过realloc也返回一个值p1(地址)
size_t a1 = _msize(p1);//_msize()就是输出malloc申请空间的大小
printf("%d", a1);
注意点: 和 malloc一样, 最重要的就是别越界,都是操作指针的!
1.3.4.5.3 msize函数
就是输出malloc空间的大小
查看 malloc申请的空间的字节数
1.3.4.5.3 calloc与malloc怎么选择
- 使用场景
calloc申请数组挺好用
其他的数据结构,比如链表,树,图,一次申请sizeof(节点),这些用malloc更合适。
- 初始化
calloc清零方便省事儿malloc不清零
- 效率问题
calloc会初始化内存,所以申请的内存数很多的时候,效率会低一点点。毕竟有时候内存不需要初始化,malloc这就效率高一些,也属于强行解释,效率问题也是微乎其微。