1.存在动态内存开辟的原因
在之前我们学到的开辟内存的方法有:
int a = 20;//向内存申请4个字节。
int arr[10];//向内存申请40个字节。
但是这样开辟的内存大小已经固定了不能修改,而且数组在声明的时候必须指定大小!但是往往在编辑代码的时候我们只有在运行程序的时候才会知道所需的内存至少要多大,以上的开辟内存的方式就显得很无力,所以这就是存在动态内存开辟的原因!
2.动态内存函数
- malloc
- free
- calloc
- realloc
1.malloc
void* malloc (size_t size);
malloc函数在使用的时候,只有一个参数,该参数就是想要开辟内存的大小,单位为字节,返回值如过开辟成功则返回开辟好的空间的起始地址,若开辟失败则返回NULL(所以应该检查指针是否为NULL)。因为返回值的类型是void*所以使用时应强制类型转换成想要的类型。该函数在开辟内存后并不会初始化空间,需要程序员手动初始化。注意:使用该函数要引用头文件stdlib.h,内存是在堆区上开辟的!
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int* ptr = (int*)malloc(40);
if (ptr == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
return 0;
}
这段代码是向内存申请了40是个字节并交予ptr来管理。 如果开辟失败指针为空,我们可以利用strerror函数来打印错误原因!
但是,如果开辟成功当程序结束后申请的这块空间并没有还给操作系统,会造成内存泄漏,所以存在函数free可以把ptr所指向的空间释放还给操作系统。其中free函数:
void free (void* ptr);
它只有一个参数,就是想要释放空间的指针(只能释放动态内存开辟的空间)。所以代码应修改为:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int* ptr = (int*)malloc(40);
if (ptr == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
free(ptr);
ptr = NULL;
return 0;
}
2.calloc
void* calloc (size_t num, size_t size);
calloc函数与malloc大同小异,第一个参数为要开辟的次数,第二个参数为要开辟一次内存的大小,总大小为开辟的次数乘以开辟一次内存的大小。返回值同样,与malloc函数一样开辟成功返回起始地址,失败则返回NULL。但是它开辟出来的内存是已经被初始化的。注意:也要引用头文件stdlib.h,内存在堆上开辟。代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int* ptr = (int*)calloc(10,sizeof(int));
if (ptr == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
free(ptr);
ptr = NULL;
return 0;
}
这段代码开辟了10个整型大小的空间交予指针ptr管理,与malloc大同小异。但是开辟的内存都已经初始化成0了。
3.realloc
void* realloc (void* ptr, size_t size);
realloc函数是用来修改内存大小的一个函数(被修改的内存应是有内存函数开辟出来的内存),第一个参数是要修改内存的起始地址,第二个参数是修改后这块内存的大小。realloc函数会在起始地址地址后面寻找内存是否够被修改后的大小,如果够,则返回值为起始地址,如果不够会在堆区的另外一个地方足够大小的内存把该内存的起始地址作为返回值并且把原地址处的数据拷贝到新的地址处,如果开辟失败则返回NULL。
即有两种情况:
情况1:原空间后有足够大空间
情况2:原空间后无足够大空间
所以在使用realloc函数调整内存空间时应注意利用中间变量来防止调整失败原空间的指针“迷路”被置为空指针失去原空间的管理权限,所以应该这样用此函数,代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int* ptr = (int*)malloc(100);
if (ptr == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//不使用中间变量
ptr = (int*)realloc(ptr, 1000);//如过调整内存失败ptr就会被置为NULL失去对于原空间的管理!
//应该使用中间变量
int* tmp = NULL;
tmp = (int*)realloc(ptr, 1000);
if (tmp != NULL)
{
ptr = tmp;
}
free(ptr);
ptr = NULL;
return 0;
}
3.一些常见的动态内存错误
1.对于NULL指针解引用操作。
int* ptr = (int*)malloc(100);
*ptr = 20;
return 0;
此段代码,可能由于malloc函数开辟内存失败返回NULL而后又对ptr解引用,导致程序错误。所以我们应该先判断指针是否为NULL。
修改后代码为:
int* ptr = (int*)malloc(100);
if(ptr==NULL)
{
return;
}
*ptr = 20;
return 0;
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的时候越界访问
}
free(p);
}
此段代码由于没有注意开辟空间的大小,进行了越界访问,会导致程序错误!应注意!
3.free对于动态开辟的内存空间部分释放
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
因为此段代码,的指针指向的不是动态内存的起始位置,找不到开辟的所有空间,free函数释放不了空间,程序报错!
4.对于非动态内存空间的free释放
void test()
{
int arr[10];
free(arr);//arr为非动态开辟的内存的起始地址
}
程序同样会报错!
5.对同一块动态内存free多次
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
由于第一次释放后p就没有指向动态开辟的内存了,如果再一次释放则程序会出错!
6.动态开辟空间后未释放空间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
return 0;
}
会造成内存泄漏!所以动态开辟的内存空间一定要正确释放!
4.柔性数组
1.什么是柔性数组
正所谓柔性数组,是指一个结构体中最后一个成员可以是一个不指定大小的数组。
//第一种
struct ABC
{
int a;
int arr[];
};
//第二种
struct ABC
{
int a;
int arr[0];
};
有两种写法第一种几乎所有编译器都通用,第二种有些编译器不能使用。
2.柔性数组的特点
第一条:因为柔性数组的大小是不确定的,所以结构体的大小不包括柔性数组的大小。
第二条:柔性数组前必须至少有一个成员。
第三条:有柔性数组的结构体利用内存开辟函数分配内存时,开辟的内存应该大于结构体的大小。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct ABC
{
int a;
int arr[];//柔性数组
};
int main()
{
printf("%d\n", sizeof(struct ABC));//输出的是4
return 0;
}
3.柔性数组的使用
//代码1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct ABC
{
int a;
int arr[];
};
int main()
{
struct ABC* ptr = (struct ABC*)malloc(sizeof(struct ABC) + 100 * sizeof(int));
if(ptr==NULL)
{
return 1;
}
ptr->a = 100;
int i = 0;
for (i = 0; i < 100; i++)
{
*(ptr->arr + i) = i;
}
free(ptr);
ptr = NULL;
return 0;
}
4.柔性数组的优势
柔性数组的内存空间是紧接着结构体后面的,不会造成内存空间碎片化,如果不是使用柔性数组用的是指针,则给指针动态开辟空间时结构体与指针管理的空间并不连续,会造成内存空间碎片化!同时由于内存的连续,也可提高运行速度!
//代码2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct ABC
{
int a;
int* arr;
};
int main()
{
struct ABC* ptr = (struct ABC*)malloc(sizeof(struct ABC));
if(ptr==NULL)
{
return 1;
}
ptr->a = 100;
ptr->arr = (int*)malloc(sizeof(int) * 100);
if(ptr->arr==NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 100; i++)
{
*(ptr->arr + i) = i;
}
free(ptr->arr);
free(ptr);
ptr = NULL;
return 0;
}
例如代码1与代码2,一个使用柔性数组一个使用指针,明显代码1分配内存更加方便,并且还可以避免内存碎片化!
一键三连哦!