在声明数组的时候,必须用一个编译时常量来指定数组的长度,但是数组的长度往往在编译的时候才能确定,这样可能会有遇到下面这两种情况:
1.有可能数组长度不够。
2.可能会浪费大量的空间。
例如:如果存在一个通讯录,通讯录的大小事先已经确定(1000),对于一些人可能只会使用100个单位,
对于一类人,可能会认为1000个单位不够使用
我们可以使用动态内存分配来解决这个问题,动态内存可以随时开辟一段空间,并且可以随时根据要求去调整该空间的大小。
不过需要注意:但是动态内存分配只能调整动态开辟出来的空间大小,并不能随意调整其他空间的大小,因为动态内存分配的空间和其他变量分配的空间不属于同一块空间
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数
malloc
该函数的头文件:stdlib.h
该函数的原型
void* malloc (size_t size);
//void类型的返回值类型利于我们返回任意类型的指针
//size_t 单位是字节,意味着我们会申请出一块以字节为单位的空间
注意:
该函数的作用是在内存中的动态内存区分配一个长度为size的连续空间。
如果开辟空间成功,会返回出这个新空间的首字节地址。
如果开辟空间失败,函数会返回一个空指针。
下面我们来使用一次该函数(我们使用指针来接收返回值):
#include
#include
int main()
{
//开辟十个整形的空间
int * p = (int *)malloc(40);
//应该尽量强制转换成接收指针的类型,这样更规范。
return 0;
}
由于我们不知道这个空间是否开辟成功,所以我们需要判断返回的指针是否是空指针。
#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int * p = (int *)malloc(40);
if(NULL == p)
{
printf("%s\n",strerror(errno));//使用sttrerror 函数可以打印出开辟空间错误的原因。
return 0;//如果开辟空间失败就退出。
}
return 0;
}
在空间开辟成功后,我们就可以访问该空间了(我们可以使用指针来访问)
#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int * p = (int *)malloc(40);
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d\t", *(p+i));
}
//释放
free(p);
p = NULL;
return 0;
}
上面的代码段最后我们使用了free
函数,使用该函数的目的是释放掉刚才所申请的空间。下面详细的介绍该函数的使用:
free
该函数的原型是:
void free (void* ptr);
该函数的参数是指向由动态内存开辟的空间(包括malloc
、 calloc
、 realloc
开辟)的指针
free
函数的作用原理是:使用该函数后,参数的那一个指针指向空间的操作(访问)权限归还给操作系统,我们不能再次使用该空间
free
函数的使用
#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int * p = (int *)malloc(40);
//尽量使用强制类型转换
if(NULL == p)
{
printf("%s\n",strerror(errno));
return 0;
}
//使用这段空间
//...
//释放这段空间
free(p);
p = NULL;
return 0;
}
NULL
在实际操作中,我们只使用free函数将该空间释放是不够的。分析该段代码:我们在申请空间的时候创建了一个指针变量用来接收返回的地址,但是在释放了动态空间后,该指针仍然指向那一块空间。这会造成非法访问(因为我们已经没有权限再次使用那块空间),所以我们在释放空间后需要将该指针变量赋为空指针,这样更安全。
注意:
每次使用完动态开辟的空间后都需要将该空间释放。如果没有释放,那么那一块空间将会在程序结束的时候才会释放(程序没有结束的时候该空间就不能有其他的作用)。
calloc
我们已经知道malloc
函数可以开辟空间,其实不止这一个函数,我们还有两个函数也可以开辟空间,其中一个是calloc
该函数的使用方法和malloc
不同
该函数的原型是:
void* calloc (size_t num,size_t size);
//第一个参数是 开辟元素的个数
//第二个参数是 每一个元素的大小
除了参数不同外,该函数还有一个特点:在开辟空间后会将这个空间全部初始化为0
,然后才返回该空间的起始地址
calloc
函数#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int * p = (int *)calloc(10,sizeof(int));
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\t", *(p+i));
}//观察初值
printf("\n");
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}//重新赋值
for (i = 0; i < 10; i++)
{
printf("%d\t", *(p+i));
}//观察赋值后的情况
//释放
free(p);
p = NULL;
return 0;
}
我们可以看到在观察初值的时候,每一个值都是0,这也验证了前面说的calloc
函数会在开辟好空间后将整个空间全部初始化为0
.
realloc
该函数的原型
void* realloc(void* ptr, size_t size);
//第一个参数是一个指针,指向由动态内存开辟出来的空间的起始地址
//如果第一个参数是空指针,那么该函数的使用方法和malloc相同
//第二个参数 要访问的空间的大小
该函数的特点:
NULL
),也可以调整动态空间(第一个参数不为NULL
).使用方法和malloc一样:开辟成功的话会返回新空间的首地址,开辟失败会返回空指针
#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int* p = (int*)realloc(NULL, sizeof(int) *10);
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}//重新赋值
for (i = 0; i < 10; i++)
{
printf("%d\t", *(p + i));
}//观察赋值后的情况
//释放
free(p);
p = NULL;
return 0;
}
可以调整前面申请的空间的大小:
#include
#include
#include
#include
int main()
{
//开辟十个整形的空间
int* p = (int*)malloc( sizeof(int) *10);//第一次申请空间
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
int* ptr = realloc(p, sizeof(int) * 20);//利用realloc将前面申请的空间扩大一倍
if (NULL == ptr)
{
printf("%s\n", strerror(errno));
return 0;
}
p = ptr;
for (i = 0; i < 20; i++)
{
*(p + i) = i;
}
for (i = 0; i < 20; i++)
{
printf("%d\t", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
我们注意到在使用realloc函数的时候我们仍然会用一个指针去接收返回值的,不过,为什么我们不直接用第一次申请空间的指针区接收这个返回值?
我们需要先来了解realloc函数使用时会遇到的几种情况:
情况1:第一次申请的空间后面有足够的空间,该函数就在这段空间后面开辟新的空间,
情况2:第一次申请的空间后面没有足够的空间,该函数会在一个足够的位置创建
情况3:没有满足条件的内存空间,就会返回一个空指针
现在我们就能理解为什么我们在使用realloc函数区调整一个空间的时候会去利用一个新的指针变量:
如果我们两次使用同一个指针变量,那么当使用realloc函数返回空指针的时候,我们的指针变量就被赋值为NULL,
无法再指向我们原来的数据,我们也无法再找到之前的数据
#include
#include
#include
int main()
{
int* p = malloc(INT_MAX);
int i = 0; for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
return 0;
}//这段代码向内存申请了一段很长的连续空间,内存无法相应该需求,就会返回空指针,此时我们利用空指针去访问空间按,会造成非法访问。
所以我们应该在申请空间后判断是否为空指针,利用这一个代码段
int* p = malloc(INT_MAX);
if(NULL == p)
{
printf("%s",strerror(errno));//出现问题还可以打印出问题的原因在结束程序。
return 0;
}
我们在申请空间之后应该清楚的知道该空间有多大,不能访问我们申请的空间之外的内容
#include
#include
#include
#include
int main()
{
char * p = (char *)malloc(10);
if(NULL == p)
{
printf("%s",strerror(errno));//出现问题还可以打印出问题的原因在结束程序。
return 0;
}
int i = 0;
for(i = 0; i <= 10; i++)//在访问*(p+10)的时候会造成内存非法访问
{
*(p+i) = 'a'+i;
}
for(i = 0; i <= 10; i++)
{
printf("%c ",*(p+i));
}
free(p);
p = NULL;
return 0;
}
free只能释放用动态内存开辟的空间
int main()
{
int a = 10;
int p = &a;
.
.
.
free(p);
p = NULL;//使用的时候应该注意不能将普通变量free
return 0;
}
使用free去释放动态内存开辟空间的一部分
#include
#include
#include
#include
int main()
{
char * p = (char *)malloc(40);
if(NULL == p)
{
printf("%s",strerror(errno));
return 0;
}
//将前五个数据初始化为1 2 3 4 5
int i = 0;
for(i = 0; i <5;i++)
{
*p = i+1;
p++;
}
free(p);//这时候指针p不在指向开辟的空间的起始位置
p = NULL;
return 0;
}
这是无法实现的,在释放动态开辟空间的时候,只能释放整个空间(free参数只能时前面开辟的空间的起始地址);
第一种:在p 赋为空指针后再次释放,不会报错
#include
#include
#include
#include
int main()
{
char * p = (char *)malloc(40);
if(NULL == p)
{
printf("%s",strerror(errno));
return 0;
}
//...
//...
free(p);
p = NULL;
free(p);
return 0;
}
第二种:连续释放了两次指针p(中间没有将p赋值为空指针) ,会出现错误。
#include
#include
#include
#include
int main()
{
char * p = (char *)malloc(40);
if(NULL == p)
{
printf("%s",strerror(errno));
return 0;
}
//...
free(p);
//...
free(p);
return 0;
}
所以我们如果每次释放完该控件,就去将指针赋值为空指针,就不会出现这个问题
我们申请的空间如果不能及时释放,那么该空间就只会在该程序结束的时候才会被自动释放,在此之前,这块空间无法再被利用,会造成内存泄漏。内存泄漏会增加程序的体积,可能会导致系统或者程序崩溃。
在C99中,结构的最后一个元素允许时未知大小的数组,这就叫做柔性数组成员
#include
struct S{
int n;
int arr[];//也可以这样使用int arr[0];
};
//结构的最后一个成员时数组,并且这个数组没有指定大小,这个成员就是柔性数组成员
int main()
{
return 0;
}
结构中柔性数组成员前面至少应该还有一个其他成员
柔性数组大小不纳入结构体大小的计算
包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预取大小
我们得到了这样一块动态内存空间,前面了解到我们可以通过realloc
函数去调整动态内存分配的空间,所以我们可以使用realloc
函数来动态调整柔性数组的大小
记住:在使用完后仍然需要释放动态分配的空间
有利于释放
访问速度更快