前言
当局部变量被创建时,会在栈区开辟空间。而当我们动态申请空间时,会在堆区开辟空间。每个动态申请的空间,如果开辟成功,则会返回这块空间的地址,否则会返回NULL,因此我们有必要对接收这块空间的指针进行判空操作,并且必须在使用完成后释放该空间并置空指向这块空间的指针。三个动态开辟空间的函数使用的头文件都是#include<stdlib.h>
一、malloc
malloc函数是按字节在堆区动态开辟出一块空间,如果开辟成功,则返回这块空间的地址,如果开辟失败,则返回NULL
1.1malloc函数原型
返回类型为void*,意思就是可以返回任意类型的指针,实际在使用时可以按照我们的需要强制类型转换,将void*转换为我们需要的指针类型,size就是字节大小,可以直接输入常量数字,也可以使用sizeof的返回值
1.2malloc函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(40);
//动态申请了40个字节大小的空间,将返指针类型转化为int*类型,并用int*的
//指针来接收
if (p == NULL)
//判断动态申请是否成功,是很有必要的一步
{
printf("%s", strerror(errno));//返回错误信息,
//可直接打印开辟失败,无需使用函数
return 1;
}
for (int i = 0; i < 10; i++)//赋值
{
*(p + i) = i + 1;
}
for (int i = 0; i < 10; i++)//输出
{
printf("%d ", *(p + i));
}
free(p);
//释放p指针指向的动态申请的空间
//动态申请的空间,在使用后必须释放,不然可能会引发问题
p = NULL;//在释放了动态申请的空间后,指针任然指向动态申请的空间,
//因此我们将将指向已经被释放的动态申请的空间的指针置空
}
二、calloc
按每个元素的字节大小开辟n个元素大小的空间,并全部初始化为0,开辟成功则返回这块空间的地址,否则返回NULL
2.1calloc函数与malloc函数的区别
calloc函数的使用和malloc极其相似,它们的区别只有两点
1.函数参数不同 2.calloc函数在动态开辟空间的同时,会将空间里的内容全部赋值为0,而malloc函数则只开辟,没有初始化
2.2calloc函数原型
num 表示个数,size表示字节大小,calloc函数就是开辟num个大小为size的空间,并初始化为0,返回类型任然为void*,同样在具体使用时,可以按照需求进行强制类型转换
2.3calloc函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p =(int*)calloc(10, 4);
//开辟10个字节大小为4的空间,将返回类型强制转换为int*后,
//用int*的指针来接收
if (p == NULL)//判断空间是否开辟成功
{
printf("%s", strerror(errno));//返回错误信息,
//可直接打印开辟失败,无需使用函数
return 1;
}
for (int i = 0; i < 10; i++)//赋值
{
*(p + i) = i + 1;
}
for (int i = 0; i < 10; i++)//输出
{
printf("%d ", *(p + i));
}
free(p);//释放p指针指向的动态申请的空间
p = NULL;//将指向已经被释放的动态申请的空间的指针置空
}
三、realloc
开辟空间,对已经动态开辟好的空间进行空间扩大或者空间缩小,开辟成功则返回空间地址,失败则返回NULL
3.1realloc函数的原型
memblock是我们要调整的空间的地址,size是调整后的空间大小(按字节算)
3.2realloc函数的使用
在使用realloc函数的时候,可能会有两种情况,
1.需要被调整的空间后如果有足够空间,那么直接在被调整空间后直接追加空间,原有的数据不做更改,如果成功则返回原本空间的地址,失败返回NULL
2.需要被调整的空间后如果没有足够的空间,则重新寻找空间开辟,并将原本的数据拷贝到新的空间,如果成功,则返回新空间的地址,失败则返回NULL
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p =(int*)calloc(10, 4);
//开辟10个字节大小为4的空间,将返回类型强制转换为int*后,
//用int*的指针来接收
if (p == NULL)//判断空间是否开辟成功
{
printf("%s", strerror(errno));//返回错误信息,
//可直接打印开辟失败,无需使用函数
return 1;
}
for (int i = 0; i < 10; i++)//赋值
{
*(p + i) = i + 1;
}
for (int i = 0; i < 10; i++)//输出
{
printf("%d ", *(p + i));
}
int* pp = (int*)realloc(p, 80);
//对原本大小为40个字节的空间进行扩容,新空间的大小为80字节
if (pp == NULL)//判空
{
printf("%s", strerror(errno));
return 1;
}
else//如果开辟成功
{
p = pp;//考虑到realloc开辟空间的两种情况,因此在开辟成功时,我们用
//p指针指向新空间的地址
for (int i = 10; i < 20; i++)//赋值
{
*(pp+ i) = i + 1;
}
for (int i = 10; i < 20; i++)//输出
{
printf("%d ", *(pp + i));
}
}
free(p);//释放p指针指向的动态申请的空间
p = NULL;//将指向已经被释放的动态申请的空间的指针置空
}
四、常见的动态内存错误
4.1对空指针的解引用操作
当我们不对p进行判空操作时,那么有可能malloc动态申请空间失败,返回NULL,然后指针P指向NULL,而直接对一个空指针进行解引用操作,可能会导致程序异常终止或拒绝服务
4.2对动态开辟空间的越界访问
我们只开辟了10个整型数据的空间,赋值时却硬要赋值11个数据,程序同样也报错
4.3对非动态开辟空间的free释放
free函数本就是针对动态开辟空间,用来释放在堆区动态开辟的空间,既然不是动态开辟的空间,却硬要使用free,那岂不是驴唇不对马嘴,很明显也是错误的,具体如下
程序直接报错
4.4使用free释放动态开辟空间的一部分
既然动态开辟空间有它相应的规范,即与开辟对应的释放,那么可想而知,我们只释放一部分空间也是不正确的,具体如下
指针p指向malloc的40个字节大小空间的地址,p是int*类型,++后跳过四个字节,此时再对p进行释放,会造成只释放动态开辟空间的一部分,程序也是立即报错
4.5对同一块内存的多次释放
对同一块内存的多次释放肯定是不合理的,我们在第一次释放的时候,对应的空间已经被销毁,我们再去释放一块已经被销毁的空间,岂不是没事找事
具体结果如下,
当对动态开辟的空间进行正常的一次释放时,程序可以正常运行
当重复释放时,程序立刻报错
4.6动态开辟空间忘记释放
虽然动态开辟的空间即使我们不主动释放,在整个程序结束后程序依然会帮助我们释放,但是有些程序可能是24小时不间断的运行的,对于这种情况如果动态开辟的空间被我们忘记释放,很可能会造成内存泄漏的问题,因此我们一定要牢记与动态开辟空间相对应的空间销毁
五、柔性数组
5.1什么是柔性数组
请看
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct Student
{
int c;
char b;
int a[];//int a[0]
// int a[]就是一个柔性数组
}S;
int main()
{
S* s = (S*)malloc(sizeof(S) + 10 * sizeof(int));
printf("%u ", sizeof(S));//8
}
我们定义了一个结构体,并对它进行类型重命名,结构体中的最后一个成员是a[],并且存在其他类型的成员,其中a[]就是柔性数组,因为没有给它分配数组元素个数,或者说默认其元素个数为空,所以不会给其分配空间
柔性数组需要满足如下几点要求
1.结构中的柔性数组成员前面必须至少一个其他成员。2.柔性数组必须写作a[]或者a[0],前者更优
柔性数组的特点
1.sizeof 返回的这种结构大小不包括柔性数组的内存。
可以看到确实没有包含
2.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
我们在用malloc动态申请空间时,前面的空间会分配给结构体的,后面的空间则会分配给柔性数组
5.2柔性数组的使用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
typedef struct Student
int c;
char b;
int a[];//int a[0]
// int a[]就是一个柔性数组
}S;
int main()
{
S* s = (S*)malloc(sizeof(S) + 10 * sizeof(int));
//malloc开辟空间,柔性数组分配了10个整型大小的空间
if (s == NULL)//判空
{
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++)//赋值
{
s->a[i] = i;
}
for (int i = 0; i < 10; i++)//输出
{
printf("%d ", s->a[i]);
}
S* p = (S*)realloc(s, sizeof(S) + 20 * sizeof(int));
//柔性数组之所以叫柔性数组,是因为它的大小可以随意调整
if (p == NULL)//判空
{
printf("%s", strerror(errno));
return 1;
}
else
{
s = p;//非空则将地址给p
}
free(s);
s = NULL;
}
5.3柔性数组的好处
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
typedef struct Student
{
int c;
char b;
int* a;
}S;
int main()
{
S* s = (S*)malloc(sizeof(S) );
if (s == NULL)
{
printf("%s", strerror(errno));
return 1;
}
s->a = malloc(sizeof(int) * 10);
S* p = (S*)realloc(s->a, 20 * sizeof(int));
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
else
{
s->a=p;
}
free(s->a);
free(s);
s = NULL;
}
柔性数组能够办到的事,我们用一个指针也可以办到,如上代码
只不过相比柔性数组可以一次malloc为结构体和柔性数组开辟空间,一次free释放掉所有空间,结构体里的指针需要在为结构体malloc空间后,再次malloc让其指向新申请的空间,同时,在释放时,必须先释放指针指向的空间,再释放结构体指针指向的空间
而malloc申请的空间可能是不连续的,多次的malloc会造成更多的空间浪费,同时操作越多错误也可能会越多
总结一下就是,柔性数组更加便于内存释放,以及一次malloc申请的空间是连续的,相对来说有益于提升访问速率