目录
一、为什么要动态内存开辟
二、动态内存开辟的函数
一、malloc
二、free
二、 calloc
三、realloc
三、动态内存常见的错误
1、动态内存开辟出来的空间忘记释放
2、对同一块动态内存空间进行多次释放
3、使用free释放动态开辟的内存中的一部分
4、对非动态内存开辟的空间进行free
5、对动态开辟的空间进行越界访问
6、对空指针进行解引用操作
四、柔性数组
我们一般在创建变量的方式是:
int main()
{
int x = 10;
int str[5] = { 1,2,3,4,5 };
return 0;
}
像这样,我们创建变量的大小是固定的,数组的长度在编译前必须指定大小, 无法在编译时开辟空间,这时候我们就可以试试动态内存开辟。
1、函数声明
void* malloc(size_t size);
2、函数参数
size —— 要开辟的内存块的大小,以字节为单位。类型是size_t,就是无符号整形unsigned int。
它是用来向内存申请一块连续的空间,返回类型是void*的指针,即返回开辟出来的空间的地址,因为返回类型是void*,所以要强制类型转换成想要的类型。
但是这个不是百分百能开辟成功,也可能开辟失败,开辟失败的话,就会返回空指针NULL,所以,我们每次用malloc开辟空间后,使用这块空间前都要判断一下,看是不是空指针,如果不是就使用,如果是,就报错,并不再使用这块空间。
如果参数为0的话,即开辟的空间大小为0,这取决于编译器,在malloc的标准中是未定义的。
1、函数声明
void free (void* ptr);
2、函数参数
ptr —— 指向以前使用或分配的内存块的指针。
它是和malloc搭配使用的,malloc用来开辟空间,而free就用来释放这个空间,所谓有借有还再借不难嘛。
注意一定要搭配使用,及时释放掉这块空间,否则会导致内存泄漏的问题。
但也不是说只要两个都出现了就一定不会出bug,如果free的参数不是动态内存开辟出来的空间,那就会报错,如果是空指针的话,就不会做出反应。
3、举例
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
arr[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);//输出结果为0 1 2 3 4
}
free(arr);
arr = NULL;
return 0;
}
1、函数声明
void* calloc (size_t num, size_t size);
2、函数参数
num —— 要分配的元素数。
size —— 每个元素的大小
功能和malloc差不多,都是从堆上开辟空间的,不同的地方就是malloc开辟出来的空间里面的值是随机值,需要给它赋值,而calloc开辟出来的空间是直接初始化为0。
它和malloc一样也是需要和free搭配使用。
3、举例
#include
#include
int main()
{
int* arr = (int*)calloc(sizeof(int), 5);
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);//输出0 0 0 0 0
}
free(arr);
arr = NULL;
return 0;
}
1、函数声明
void* realloc (void* ptr, size_t size);
2、函数参数
ptr —— 想要重新分配大小的内存空间的地址(指针)
size —— 想要重新分配多少空间的字节数。
这个函数是用来为地址重新分配空间的,如果参数是个空指针,那么它就会分配一个空间,并返回起始地址,可以和malloc或者calloc搭配使用,但是不要忘记释放掉这块空间。
另外,如果ptr是NULL,就相当于malloc(size),如果size是0,就相当于free(ptr)。
3、举例
#include
#include
int main()
{
int* ptr = NULL;
int* arr = (int*)realloc(ptr, 5 * sizeof(int));//ptr为空指针,这里相当于malloc(5 * sizeof(int))
if (arr == NULL)
{
perror("main()");
return 1;
}
ptr = arr;
for (int i = 0; i < 5; i++)
{
ptr[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", ptr[i]);
}
free(arr);
arr = NULL;
return 0;
}
开辟出来的空间一直不释放,会导致内存泄漏。
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
arr[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
//未使用free进行释放
return 0;
}
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
arr[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
free(arr);
free(arr);//重复free
arr = NULL;
return 0;
}
这时候编译器就会发现了这个arr没有初始化,里面是随机值,是已经被释放了的 ,再进行释放就会报错。
就是说指针位置不能改变,如果想要遍历,可以再创建一个指针并赋值给这个指针。
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
arr++;//arr改变了位置
free(arr);
arr = NULL;
return 0;
}
#include
#include
int main()
{
int a = 10;
int* pa = &a;
free(pa);//非动态开辟的空间
pa = NULL;
return 0;
}
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
arr[i] = i;
}
for (int i = 0; i <= 5; i++)
{
printf("%d ", arr[i]);//越界访问,打印出来的是个随机值
}
free(arr);
arr = NULL;
return 0;
}
#include
#include
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
arr[i] = i;
}
for (int i = 0; i <= 5; i++)
{
printf("%d ", arr[i]);//越界访问,打印出来的是个随机值
}
free(arr);
arr = NULL;
return 0;
}
柔性数组是在结构体中最后一个元素允许未知大小的数组,但它仅限于C99标准。
我们来看看一个包含柔性数组的结构体的大小
#include
#include
typedef struct array
{
int x;
int arr[];//柔性数组
}array;
int main()
{
printf("%d\n", sizeof(array));//输出结果是4
return 0;
}
从这里我们可以推断出,结构体大小是没有把柔性数组大小算进去的,因为在创建柔性数组的时候是没有指定大小的,需要使用动态内存开辟的函数来开辟空间,并且开辟的空间应该大于结构体大小。
包含柔性数组的结构体的使用:
#include
#include
typedef struct array
{
int x;
int arr[];//柔性数组
}array;
int main()
{
int i = 0;
array* ptr = (array*)malloc(sizeof(array) + 5 * sizeof(int));
if (ptr == NULL)
{
perror("main()");
return 1;
}
ptr->x = 100;
for (int i = 0; i < 5; i++)
{
ptr->arr[i] = i;
}
printf("%d\n", ptr->x);
for (int i = 0; i < 5 ;i++)
{
printf("%d ", ptr->arr[i]);
}
return 0;
}
这里注意,结构体中的柔性数组成员前面必须至少有一个其他成员。
另外,上面这个结构体也可以写成这样:
#include
#include
typedef struct array
{
int x;
int* arr;//柔性数组
}array;
int main()
{
int i = 0;
array* ptr = (array*)malloc(sizeof(array));
if (ptr == NULL)
{
perror("main()");
return 1;
}
ptr->x = 5;
ptr->arr = (int*)malloc(ptr->x * sizeof(int));
if (ptr->arr == NULL)
{
perror("main()");
return 1;
}
for (int i = 0; i < 5; i++)
{
ptr->arr[i] = i;
}
printf("%d\n", ptr->x);
for (int i = 0; i < 5 ;i++)
{
printf("%d ", ptr->arr[i]);
}
free(ptr->arr);
ptr->arr = NULL;
free(ptr);
ptr = NULL;
return 0;
}
这样的话就没有第一个好,因为开辟空间分两次开辟,释放空间也需要分两次释放,这样显得第一种更方便释放内存,并且,开辟空间是连续开辟的,提高访问速度,如果分两次,那这两块空间就有可能不是连续的,会有内存碎片,虽然影响不是很大。