动态内存管理
为什么存在动态内存管理?
在过去我们学的数组或者直接使用变量来开辟空间,这个空间是处于栈上的,在空间开辟的时候大小就是固定的,不能改变,因此当开辟的空间不够时,这种方式就无法满足了,故我们引入动态内存的开辟方式。
动态内存函数
malloc和free
void* malloc(size);
这句代码的含义是向内存中申请一块连续的空间,是位于堆上的,并且返回一个这块空间的指针。如果开辟成功,返回一个这块空间首地址的指针,如果开辟失败,返回一个NULL指针,因此,这里需要对malloc后的返回值进行一个检查。malloc的返回值是一个void*,因此返回值的类型我们是可以自己定义的,如果参数size的值为0,malloc的行为标准是未定义的,取决于编译器。
void free(void* ptr);
free函数是用来释放动态开辟的内存的,如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的,如果ptr是NULL指针,则函数什么都不做。
malloc和free都声明在stdlib.h头文件中
calloc
原型:
void* calloc(size_t num, size_t size)
功能:
为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0,这里注意一个误用,calloc只能初始化为0,不能初始化为其他数字。与malloc的区别也只是有着初始化为0的区别。
realloc
原型:
void* realloc(void* ptr,size_t size);
功能:
如果在过去我们感觉申请的空间太小,relloc可以再申请的空间,如果第二次申请的空间比较小,再内存中原本的地址能连续的申请成功,那么ptr的地址是不会改变的,但是如果申请的新空间比较大,在原本的位置申请的话无法连续,那么就会重新开辟一个空间,并且系统会把原本的值赋到新空间上,并把原来的空间释放掉,ptr是需要扩容的空间的首地址,size是调整后的大小,一般是在原来基础上扩容1.5倍-2倍。返回值为扩容后空间的首地址。
常见的动态内存的错误
1.对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
申请完空间需要先判断指针p是否为NULL。
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);
}
只申请的40个字节的空间,因此只能到下标9。
3.对非动态内存开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
free主要是释放malloc出来的空间,这类空间是在堆上的,而代码中的空间是在栈上开辟的。
4.使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
内存释放要释放就一起释放。
5.对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6.动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
柔性数组
原型:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
柔性数组的特点:
- 除了柔性数组之外,结构体中必须还包含至少一个其他成员,并且柔性数组成员在结构体的最后。
- 使用sizeof返回结构体的大小时,不计算柔性数组的内存。
- 柔性数组的大小用malloc进行分配,分配多出来的空间就是柔性数组的大小,因此,分配的空间应该大于结构体的空间。