文章目录
一、今天我么一起了解一下什么是动态内存分布
今天是军训完的第一天,随着了解的东西多了,愈发觉得自己有多么的无知,压力山大,不学不知道(有一句话叫:什么什么鬼,反正忘记掉了,主要讲的就是一个人只有学的多了,才会意识到自己有多么的无知),虽然我还没学得多,但是我已经了解到了自己的无知,所以我们要继续
1.首先我们要区分一下什么是内存什么是动态内存
(1.)首先我们要知道一个内存是分为3个区的(栈区,堆区,静态区)
栈区上存放的是(局部变量和函数的形式参数)
堆区上存放的是(我们今天要学的动态内存分配)
静态区上存放的是(全局变量和静态变量:static int a = 10,这个static的作用就是让我的延长变量a的生命周期,使其出了它的作用域还能起作用)
(2.)然而目前我们已知的内存使用方式有两种
一种是创建一个变量例:int a =10;(一个变量的空间)
另一种是创建一个数组例:int arr[10];(另一个是开辟一块连续内存的空间)
(3.)但是因为这些变量的创建使用的内存都是一个固定的值,不能够方便的进行改变(所以就有了动态内存的概念)
(4.)以下是一幅图,让我们更好的理解什么是内存:
2.动态内存分配在堆区的使用
所以此时我们就有了详细的内存的概念,所以此时我们就可以知道我的动态内存其实就是在内存中的堆区,在进行一系列的动态内存的创建和使用过程中,我们都是离不开堆区的
(1.)首先在进行动态内存的创建时,我们会用到关于动态内存的几个库函数(malloc、calloc、realloc、free),只有利用了这几个动态内存库函数,我们才可以很好的创建出我想要的空间
(2.)所以接下来我们就进行这几个函数的使用方法的一个介绍
3.malloc库函数的使用方法
(头文件 #include<stdlib.h>)
(1.)在C语言文库中的基本使用形式是:voidmalloc(size_t size)
(2.)意思就是我用malloc向内存申请一块空间(如何申请这块空间呢?所以voidmalloc(size_t size)这个表达式的使用方法就是我可以用一个void的指针去指向我申请的那块空间的地址),只有这样我才可以拿到我的那个空间的地址(总的意思就是我向内存申请size个字节的空间,申请好之后就把地址赋给这个函数,然后这个函数用一个void指针去指向接收那块空间的地址)
(3.)所以具体的使用方法就是 malloc(10sizeof(int));(意思就是向内存申请10个整形类型的空间),所以申请完空间之后,我现在就可以用一个指针去接收,因为是整形,所以我指针也应该用整形,所以写成 int p = malloc(10sizeof(int));但是要进行一个类型的转换(因为我并不知道这个地址的类型是什么),所以写成 int p = (int ) malloc(10sizeof(int));这样就是一个非常完整的动态变量内存的创建了
(4.)这边有一个注意点,因为会有内存开辟失败的这种可能(当我开辟的内存太大时),所以下面看代码,一个malloc函数的具体使用(可以防止错误原因和提示错误原因的代码)
//1.malloc的使用
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<stdio.h>
int main()
{
//int* p = (int*)malloc(10 * sizeof(int));申请的空间在范围之内(可以正常使用)
//int* p = (int*)malloc(10*INT_MAX);申请的空间在范围外(会报错误信息,空间不够)
if (p == NULL)
{
//打印错误原因的方式
printf("%s\n",strerror(errno));
}
else
{
//可以正常使用空间的情况
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;//此时的这个p代表的是指针(指向了我开辟的地址),(也就是我的p指针变量中存放了我开辟的地址),所以当我要使用过这个地址的时候这边就一定要进行解引用(就是在找我的那个下标为i的元素的空间)
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p)
p=NULL;
return 0;
}
(5.)以上就是malloc的正常使用方法
(6.)但是还会涉及到一个问题(就是我申请了空间,但是没有把空间返还),所以这边就又涉及到一个内存释放函数
4.free库函数的使用方法
(头文件 #include<stdlib.h>)
(1.)所以这个函数就是可以起把我申请的空间给返还的作用(释放空间)
(2.)函数原型:void free(void*memblock); memblock这个就是内存块的意思,并且这个原型的意思就是我要释放那块空间就把那块空间的地址给我
(3.)例:我刚刚申请的那个动态空间,此时想要释放,写成 free§ 就行,(这样就把我的空间还给了内存),但是只是返还了内存(但此时这个(p)指针此时还在指向这块内存,所以此时就有可能导致非法访问了),所以在释放完空间后(free( p)),我最好再这步的后面再加上一步(p=NULL),把这个指针指向的空间给初始化为0,这样 p 就不再指向原来我申请的那块空间了,此时再对 p 进行操作就不会造成非法访问,这样就可以使我的代码更加安全
(4.)所以malloc和free一定要成对使用,有申请就有释放(不然就会出问题)
(5.)并且这边free释放内存只针对于动态内存的开辟与数组申请的内存块,变量申请的内存是无关的(不需要free,不然也会出问题)
(6.)在释放时释放的也就是堆区上的空间,不能释放别的区的空间(栈区,静态区)
5.calloc库函数的使用方法
(头文件 #include<stdlib.h>)
(1.)这个函数有一个特点(它会对我开辟出来的空间的字节进行初始化为0)
(2.)函数原型:
(3.)具体使用方法:calloc(10,sizeof(int));(意思为开辟10个元素,每个元素几个字节)
区别于 malloc(10sizeof(int));这个函数的使用(一个是开辟总大小是几个字节)(一个是开辟几个元素,每个元素是几个字节),完整写法int p = (int *)calloc(10,sizeof(int));
(4.)让我们看一下calloc函数的具体使用,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
输出结果如下:
(5.)这个就能充分证明,当我使用calloc函数开辟内存的时候,它会自己进行将开辟的内存的每个字节初始化为0
(6.)再总结一遍函数的作用:就是将num个大小个大小为size的元素开辟一块空间,并且把每块空间的每个字节初始化为0;
(7.)与salloc函数的区别就是calloc会在返回地址之前把申请的空间的每个字节初始化为0
6.realloc库函数的使用方法
(头文件 #include<stdlib.h>)
(1.)主要作用用来调整我的动态内存的大小
(2.)函数原型:void* realloc(void * memblack,size_t size)(意思为:我假如用malloc开辟了一块动态内存,但是现在这块内存不够用了,我想让这块内存变大,所以此时你就需要把原来开辟的的那块内存的地址告诉我void * memblack,这个就是代表把原地址告诉我,然后知道地址后,再看一下要追加多少的字节的内存,,size_t size这个就是我要追加的内存的大小的意思)
(3.)接下来我们通过代码看一下什么是内存追加
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;//此时进行对20个字节的空间进行使用
}
//此时就是我开辟了一个20个字节的空间进行使用,然而此时假如不够用了
//我就可以使用realloc来调整我的内存大小
int* p2 = realloc(p, 40);
for (i = 0; i < 10; i++)
{
printf("%2d\n", *(p2 + i));
}
}
return 0;
}
输出结果如下:
(4.)此时就能很好的看出我的realloc的作用就是在原开辟内存的后面再追加内存(因为我原来malloc开辟的内存已经使用过了,被赋值为了0, 1,2 , 3,4),后来我有追加内存到40和字节,(后20个字节并还没有使用),所以此时打印出来的就是后20个字节所对应的地址(所以表示我成功的追加了20个字节的内存)
(5.)所以此时如果想对后面追加的内存进行使用,我也是可以使用的,例:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;//此时进行对20个字节的空间进行使用
}
//此时就是我开辟了一个20个字节的空间进行使用,然而此时假如不够用了
//我就可以使用realloc来调整我的内存大小
int* p2 = realloc(p, 40);//此时的p2其实就已经包括了p的地址了
for (i = 5; i < 10; i++)
{
*(p2 + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%2d\n", *(p2 + i));//所以这边打印p2就等于在打印p,因为此时这两个内存是在一起的
}
}
return 0;
}
输出结果如下:
(6.)这就充分表明了relloc的作用就是可以追加一块内存,但是还有两个注意点:
(1.)relloc使用时的两个注意点
1.这个relloc是如何进行内存的调控的,首先是在堆区上进行,然后第一种情况是假如我的melloc开辟完指定空间的大小后,那个开辟空间的后面还有足够大小的空间(够存放relloc追加后的空间),那么此时这个空间的地址就还是原来melloc开辟时的那个地址,否则就是第二种情况
2.就是melloc开辟空间的后续空间不够大(不够relloc追加的空间),那么此时我的relloc就会带领原来那个melloc开辟的空间中所存放的数据进行搬家(就是换一个大空间(足够放下我追加后的空间的空间)),所以如果这样的话,那么此时的地址就已经被改变了,变成了一个新的地址
(7.)所以 因为上述情况,我的这个relloc最后返回的地址就有可能具有两种可能性的地址