文章目录
- Ⅰ 存在动态内存分配的原因
- Ⅱ 动态内存函数
- 1. malloc
- 2. calloc
- 3. realloc
- 4. free (重要)
- Ⅲ 常见动态内存错误
- 1. 对 NULL 指针的解引用操作
- 2. 对动态开辟空间的越界访问
- 3. 对非动态开辟内存使用 free 释放
- 4. 使用 free 释放一块动态开辟内存的一部分
- 5. 对同一块动态内存多次释放
- 6. 动态开辟内存没有释放(内存泄漏)
- Ⅳ 柔性数组
引用头文件
stdlib.h
Ⅰ 存在动态内存分配的原因
内存使用的方式
-
创建变量(开辟一块独立的空间)
- 局部变量 -> 栈区。
- 全局变量 -> 静态区。
-
创建数组(开辟一块连续的 空间)
- 局部数组 -> 栈区。
- 全局数组 -> 静态区。
上述开辟空间的方式有两个特点
- 空间开辟的大小是固定的
- 数组在定义的时候,必须指定数组的长度,它所需要的内存在编译时分配。
存在的问题
- 不能灵活的开辟空间。如果需要的空间很少,但是固定开辟的空间非常大,就会造成内存浪费。如果需要的空间很多,固定开辟的空间很小,就会造成内存不够用的情况。
- 因此就出现了动态内存函数,用来随时更改所开辟的空间的大小。
Ⅱ 动态内存函数
1. malloc
函数原型
void* malloc (size_t size);
函数功能
- 开辟一块 size 个字节大小连续可用的空间,并返回该空间的起始地址。
函数返回值
- 如果开辟成功,则返回一个指向开辟好空间的起始地址的指针。
- 如果开辟失败,则返回一个 NULL 指针。因此要检查好 malloc 的返回值。
- 返回值的类型是 void*,在使用指向开辟好的空间的起始地址的指针时,还需要转换成自己需要的指针类型。
函数用例
- 申请一块空间,用来存放 10 个整形。
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
//malloc 返回的是 void* 的指针,需要转换成对应的指针类型才能使用
return 0;
}
2. calloc
函数原型
void* calloc (size_t num, size_t size);
函数功能
- 为 num 个大小为 size 的元素开辟一块空间,并且把该空间的每个字节初始化为 0。
- 与 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
函数用例
- 开辟一块具有 10 个元素,每个元素 4 个字节的空间。
3. realloc
函数原型
void* realloc (void* ptr, size_t size);
函数功能
- 调整由 ptr 所指向的空间大小为 size 个字节。
函数参数
- ptr:指向由 malloc、calloc 所开辟的空间。
- size:新空间的大小(字节)。
realloc 函数的两种使用情况
void* realloc (void* ptr, size_t size);
- 将 ptr 所指向的空间大小重新调整为 size 个字节。
void* realloc(NULL,size_t size);
- 开辟一块有 size 个字节的空间。
函数用例
realloc 调整空间的方式
realloc在调整内存空间时存在两种情况
- 情况 1:原有空间之后有足够大的空间,扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧地址。
- 情况 2:原有空间之后没有足够大的空间,realloc 会重新找一个内存区域开辟一块符合要求的空间,返回新地址,拷贝旧数据,释放旧空间。
4. free (重要)
函数原型
void free (void* ptr);
函数参数
- ptr:指向先前用 malloc、calloc、realloc 分配的内存块的起始地址。
函数功能
- 释放由 ptr 指向的动态开辟的空间。
函数用例
- 释放由各个动态内存函数所开辟的空间。
int main()
{
int* p1 = (int*)malloc(10 * sizeof(int));
int* p2 = (int*)calloc(10, sizeof(int));
int* p3 = (int*)realloc(NULL, sizeof(int) * 10);
//......
free(p1);
free(p2);
free(p3);
return 0;
}
注意事项
- free 会将指针指向的空间释放掉,但是不会改变指针内存放的地址值。
- 也就是说,即使 free 掉了 p1 p2 p3 三个指针所指向的空间,这三个指针依然记得原先空间的起始地址。
- 因此释放掉指针所指向的空间之后,还必须要将该指针变量的值置为 NULL。
int main()
{
int* ptr = (int*)malloc(10 * sizeof(int));
//......
free(ptr); //释放掉ptr指向的空间后,ptr内存的地址值未变,还是记得原来空间的地址
ptr = NULL; //因此要主动修改ptr内存放的地址值为NULL
return 0;
}
Ⅲ 常见动态内存错误
1. 对 NULL 指针的解引用操作
- 在使用之前未判断动态内存函数的返回值是否是 NULL,如果内存开辟失败返回的是 NULL,就成了对 NULL 指针的解引用操作。
int* p = (int*)malloc(40);
*p = 20;//使用前为判断指针 p 内的值是否有效
正确做法
int* p = (int*)malloc(40);
if (NULL == p) //使用之前先判断该空间是否开辟成功
{
return 1; //1 为异常返回
}
*p = 20;
2. 对动态开辟空间的越界访问
- 动态申请的空间也有着自己的范围,不能无限制的使用。
int* p = calloc(10, sizeof(int));
if (NULL == p)
{
perror("calloc");
return 1;
}
for (int i = 0; i <= 10; i++)//当 i 到 10 时越界访问
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
3. 对非动态开辟内存使用 free 释放
- free 只能释放由 malloc、calloc、realloc 开辟的空间。
4. 使用 free 释放一块动态开辟内存的一部分
- 指向开辟空间起始地址的指针产生变动,没有从动态开辟内存的起始地址开始释放空间。
5. 对同一块动态内存多次释放
- 忘记了已经释放过动态开辟的空间,又重新释放了一遍。
int* p = (int*)malloc(10 * sizeof(int));
//......
free(p);//此时已经释放过该空间一次
//.....
free(p);//睡蒙了又释放一次
解决办法
- 在释放完动态开辟好的内存后,及时将该指针置为 NULL。之后如果再重复释放也不会产生问题。
6. 动态开辟内存没有释放(内存泄漏)
- 只记得申请空间,不记得还。
void test()
{
int* p = (int*)malloc(10 * sizeof(int));
//p 是局部变量,出了作用域就销毁,等这个函数一结束就没人再记得这块空间的起始地址了
if (true)//此时动态开辟的空间永远没机会释放了
{
return;
}
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
Ⅳ 柔性数组
- C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
struct S
{
char c;
int i;
int arr[]; //未知大小数组 - 柔性数组成员
};
柔性数组的特点
- 结构体中的柔性数组成员前面必须至少有一个其他成员。
- sizeof 返回的这种结构体大小不包含柔性数组成员的大小。
- 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
struct S
{
char c;
int i;
int arr[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);
//这 20 个字节才是分配给柔性数组 arr 的
return 0;
}