动态内存管理、动态内存函数、柔性数组
动态内存管理
我们知道创建变量、创建数组就是在内存中开辟一块空间,这块空间是固定的大小。
数组在声明时会指定数组的长度,在编译的时候会给数组分配内存,但有时我们需要的空间需要到程序运行的时候才知道,那指定数组长度在编译的时候分配空间的这种方式就不能满足要求了。
这时候就有了动态内存分配。
动态内存函数
malloc函数
void* malloc (size_t size);
size:开辟内存块的大小,以字节为单位。(size_t是无符号整数类型。)
函数作用:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
注:
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
代码示例
#include <stdlib.h>
int main()
{
int num = 4;
int* p = NULL;
//相当于创建int arr[4]大小的空间
//malloc的返回类型为void*,强制转换成int*
p = (int*)malloc(num * sizeof(int));
if (!p)
{
printf("p == NULL");
return;
}
//对开辟的空间进行初始化
int i = 0;
for (i = 0; i < num; i++)
{
*(p + i) = 0;
}
for (i = 0; i < num; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
//防止非法访问内存,因为p已经被释放
p = NULL;
return 0;
}
运行结果
0 0 0 0
free函数
void free (void* ptr);
ptr:指向以前分配了malloc、calloc或realloc的内存块的指针。
函数的作用:
解除分配内存块,先前由调用malloc、calloc或realloc分配的内存块被解除分配,使其可再次用于进一步分配。
注:
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
free函数不会改变ptr原本的值,但是ptr指向的空间已经被释放,访问ptr指向的空间会造成非法访问内存
代码示例
#include <stdlib.h>
int main()
{
int num = 4;
int* p = NULL;
p = (int*)malloc(num * sizeof(int));
//释放空间
free(p);
//防止非法访问内存,因为p已经被释放
p = NULL;
return 0;
}
calloc函数
void* calloc (size_t num, size_t size);
num:要分配的元素的个数。
size:要分配的元素的大小。
函数的作用:
分配和零初始化数组,为num个元素的数组分配一块内存,每个元素的大小为字节长,并将其所有位初始化为零。
也就是是为
num
个大小为size
的元素开辟一块空间,并且把空间的每个字节初始化为0。
与malloc的区别:
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
代码示例
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (!p)
{
printf("p == NULL");
return;
}
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
运行结果
0 0 0 0
realloc函数
void* realloc (void* ptr, size_t size);
ptr:指向以前分配了malloc、calloc或realloc的内存块的指针。或者,这可以是一个空指针,在这种情况下,会分配一个新块(就像调用了malloc一样)。
size:内存块的新大小,以字节为单位。(size_t是无符号整数类型。)
函数的作用:
重新分配内存块,更改ptr指向的内存块的大小。
注:
- 返回值指向重新分配的内存块的指针,可以与ptr相同,也可以是新位置。
- realloc在调整内存空间时存在两种情况:
- 情况1:原空间的后面有足够大的空间,在原空间后直接追加空间,原空间数据不发生改变,返回原内存地址。
- 情况2:原空间的后面没有足够大的空间,在内存另找一个合适的连续空间来使用,把原空间的数据拷贝到新空间,返回新的内存地址。
- realloc在内存空间中找不到合适的内存空间时,返回NULL,原空间不会释放可以继续使用。
代码示例
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (!p)
{
printf("p == NULL");
return;
}
//不能直接使用原空间的指针接收,如果realloc在内存空间中找不到合适的内存空间时,
//会返回NULL,原空间不会释放可以继续使用。
//如果用p接收,p就会等于NULL,就找不到原空间了
//p = (int*)realloc(p, 8 * sizeof(int));
int* ptr = (int*)realloc(p, 8 * sizeof(int));
if (ptr)
{
p = ptr;
}
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
运行结果
0 0 0 0
常见的动态内存错误
对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
对动态开辟空间的越界访问
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);
}
对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p);//错误
}
使用free释放一块动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
切记:
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
动态开辟的空间一定要释放,并且正确释放 。
柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
柔性数组的使用
方法一:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
int main()
{
//成员a相当于获得了100个整型元素的连续空间
type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
p->i = 100;
int i = 0;
for (i = 0; i < 100; i++)
{
p->a[i] = i;
}
free(p);
p = NULL;
return 0;
}
方法二:
typedef struct st_type
{
int i;
int* ptr;//柔性数组成员
}type_b;
int main()
{
type_b* p = (type_b*)malloc(sizeof(type_b));
//ptr指向了100个整型元素的连续空间
p->ptr = (int*)malloc(100 * sizeof(int));
p->i = 100;
int i = 0;
for (i = 0; i < 100; i++)
{
p->ptr[i] = i;
}
free(p->ptr);
p->ptr = NULL;
free(p);
p = NULL;
return 0;
}
方法一和方法二可以完成一样的功能,但方法一有两个好处:
- 方便内存释放,如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
- 这样有利于访问速度,连续的内存有益于提高访问速度,也有益于减少内存碎片。
柔性数组的特点
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
本文出现任何错误,欢迎留言批评指正。