这一章主要介绍4个C语言中用于动态内存分配的函数:malloc、calloc、realloc和free
动态内存分配的必要性
动态内存分配主要用来替代数组长度必须在编译时确定的缺陷。 通过同台内存分配,我们可以在程序运行时根据需要申请适当大小的内存。
malloc
malloc函数原型如下 要点
- 接收一个size_t型参数,表示开辟空间的字节数
- 函数向内存(堆区)申请一块连续可用的空间,并返回一个指针。
- 如果空间开辟成功,则返回这项这块空间的首地址,类型是void*。
- 如果空间开辟失败,则返回NULL指针。由于NULL不能解引用,在使用malloc等内存配分函数时,要检查返回值再使用。
- 如果参数为0,则结果是标准未定义的
关于void型指针 标准表示一个void类型的指针可以转换为其他任何类型的指针,只要把void*指针赋值给目标类型指针就可以完成转换(可以不写强制类型转变换)
free
函数原型如下 要点
- 该函数接受一个任何类型的参数(必须是malloc或calloc或realloc返回的地址),并将该空间释放(把空间还给操作系统),于内存分配函数配合使用。
- 不可以只释放一部分空间,如果想释放掉一部分空间,可以用realloc实现。
- 不能释放非动态内存分配的空间。
- 如果传给free的是空指针,则free什么也不做,不会报错。
- 不可将一块空间多次释放。
- free之后,只是空间被释放了,指针变量的值未改变,为避免使用野指针的错误,建议使用free之后,自行把指针指向NULL
使用示例
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p;
p = malloc(10 * sizeof(int));
if (p != NULL)
{
int i;
for (i = 0; i < 10; i++)
{
p[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
}
free(p);
p = NULL;
return 0;
}
calloc
函数原型如下 这个函数和malloc很像
要点
- 接受两个参数,一个size_t的num表示开辟元素的个数,另一个size_t型的size,表示每个元素的大小,以字节为单位。
- 如果开辟成功,返回开辟空间的首地址。
- 如果开辟不成功返回NULL。
- 返回之前把每个字节的空间都初始化为0。
realloc
函数原型如下
这个函数用于修改开辟空间的大小
要点
- 接收两个参数,一个是任意类型的指针(必须是malloc或calloc的返回值),另一个是需要开辟的空间(不是改变量)。
- 如果开辟成功,返回开辟空间的首地址。
- 如果开辟不成功返回NULL。
- 开辟的空间仍然是一块连续空间。
- 如果需要的空间比原来小,则释放后面的空间,未释放的空间保存原来的数据不变。
- 如果需要的空间比原来大,并且原来空间后面有足够的连续可用空间,则直接把后面的空间加在原来的空间后面
- 如果需要的空间比原来的大,但是原来空间后面没有足够的连续可用空间,则在另外一个地方开辟足够大小的空间,并将原来的数据拷贝到对应位置,原来的那块空间会被释放。
- 空间开辟失败会返回NULL。
- 建议用一个临时变量接收realloc的返回值,判断非NULL之后再赋值给维护空间的指针。
- 如果第一个参数是NULL,则realloc的行为和malloc一样。
使用示例
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p;
p = malloc(10 * sizeof(int));
if (p != NULL)
{
int i;
for (i = 0; i < 10; i++)
{
p[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
printf("\n");
}
int* tmp;
tmp = realloc(p, 20 * sizeof(int));
if (tmp != NULL)
{
p = tmp;
int i;
for (i = 0; i < 20; i++)
{
p[i] = i;
}
for (i = 0; i < 20; i++)
{
printf("%d ", p[i]);
}
}
free(p);
p = NULL;
}
内存泄漏
分配内存但再使用完毕后不释放将引起内存泄漏(memory leak),当内存耗尽后会造成程序/系统崩溃。只能通过重启系统解决。 对于大型程序,由于程序需要长时间运行,如果存在内存泄漏将是很严重的问题。
柔性数组
C99引入了柔性数组,所谓柔性数组,就是结构体中最后一个成员可以是一个位置大小的数组,有以下两种写法
struct S1
{
int a;
int b[];
};
struct S2
{
int a;
int b[];
};
要点
- 柔性数组前面必须至少含有一个其他类型的成员。
- sizeof计算这种结构体的大小时,不包括柔性数组。
- 使用这种结构的方法是用上面讲到的内存开辟函数开辟空间,用这种结构体指针来维护。
使用示例
#include<stdio.h>
#include<stdlib.h>
struct S1
{
int a;
int b[];
};
struct S2
{
int a;
int b[];
};
int main()
{
struct S1* p1 = malloc(sizeof(struct S1) + 10 * sizeof(int));
if (p1 != NULL)
{
int i;
for (i = 0; i < 10; i++)
{
p1->b[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p1->b[i]);
}
printf("\n");
}
free(p1);
p1 = NULL;
struct S2* p2 = malloc(sizeof(struct S2) + 10 * sizeof(int));
if (p2 != NULL)
{
int i;
for (i = 0; i < 10; i++)
{
p2->b[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p2->b[i]);
}
}
free(p2);
p2 = NULL;
return 0;
}
柔性数组的另一种实现
#include<stdio.h>
#include<stdlib.h>
struct S
{
int a;
int* b;
};
int main()
{
struct S* p = malloc(sizeof(struct S));
if (p != NULL)
{
p->b = malloc(10 * sizeof(int));
if (p->b != NULL)
{
int i;
for (i = 0; i < 10; i++)
{
p->b[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p->b[i]);
}
printf("\n");
}
free(p->b);
p->b = NULL;
}
free(p);
p = NULL;
return 0;
}
这种方法有个很大的缺点是进行了两次内存分配,如果封装成函数的代码中使用了这种方法,则用户需要用户并不知道结构体内部还有一个需要释放的空间,如果直接将结构体指针指向空间释放,那么这个里面开辟的空间就找不到了,而那块空间还没有被释放,这就造成了内存泄漏。
|