1.栈区,堆区
栈区
- 概念:栈区用于存放局部变量,形参等,栈区由系统自动分配内存,当离开作用域自动释放。
- 分配大小:栈区由高地址向低地址扩展,是连续的内存区域,空间大小一般是2M,能从栈申请的空间比较小。
- 分配速度:栈区申请内存的速度比堆区要快,当数据量比较大时有明显区别。
堆区
- 概念:堆区用于存放通过malloc,realloc,new主动申请的空间,并可以使用free来释放。
- 分配大小:堆区由低地址向高地址拓展,是不连续的内存区域,堆区可申请的空间更大
如果栈区和堆区都是一个方向拓展的,当栈区或者堆区的数据量过大,栈区的数据就会有放到堆区的问题。所以栈区是由高地址向低地址拓展的,堆区是由低地址向高地址拓展的。
2.为什么需要动态内存分配?
int a = 10 //在栈上开辟4个字节的空间
int arr[10] = {0}; //在栈上开辟40个字节的空间
创建整形变量和整形数组时开辟的空间是固定的,有时候我们需要的空间大小在程序运行的时候才能知道,使用动态内存管理可以灵活分配内存
3.如何动态内存管理?
malloc函数
malloc函数向内存申请一块size大小的连续可用空间,并返回指向这片空间的指针
int* ptr = (int*)malloc(sizeof(int)); //动态内存开辟的4个字节空间
- 如果开辟成功,则返回一个指向开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针,malloc的返回值需要检查
- 返回类型是void* ,所以malloc函数使用时要自己决定类型
- 如果参数size是0,malloc的行为是未定义的,取决于编译器
free函数
free函数是专门用于做动态内存的释放和回收,用于回收malloc,realloc,calloc函数申请的空间
int* ptr = (int*)malloc(sizeof(int));
free(ptr) //free函数回收malloc申请的空间
ptr = NULL //指针需要制空,避免变成野指针
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr为NULL,那么函数什么都不做
calloc函数
calloc函数为num个大小为size的元素开辟一块空间,并把空间的每一个字节初始化为0
int* ptr = (int*)calloc(sizeof(int)); //动态开辟了4个字节的空间,并且全部初始化为0
- calloc函数和malloc函数的区别:calloc相当于把malloc申请的空间每一个字节初始化为0
- 如果我们需要对申请的内容初始化,可以使用calloc函数
realloc函数
如果想要将申请的空间增加或者缩小为size大小,可以使用realloc函数做到动态内存大小的调整
int* ptr = (int*)malloc(sizeof(int)); //动态内存开辟的4个字节空间
int* p = (int*)realloc(ptr,2*sizeof(int)); //扩容以后动态内存变成8个字节
- realloc函数的返回值是调整之后内存的地址
- realloc函数扩容的空间比较大,后面的空间不足时,realloc函数会寻找一块新的空间并将原来内存的数据转移到新的空间,最后返回新空间的地址
- realloc函数扩容的空间比较小,后面的空间足够时,realloc函数会在原空间内存后面追加空间,原先的空间数据不变
4.常见的动态内存错误
使用malloc等函数没有检查返回值
void test()
{
int* p = (int*)maollc(sizeof(int));
*p = 4; //没有检查p指针是否为空*/正确的写法
if(p==NULL)
{
perror("malloc");
exit(-1);
} */
free(p);
p = NULL; //释放空间后,需要把p置空
}
对动态开辟的空间越界访问
void test()
{
int* p = (int*)maollc(10*sizeof(int));
if(p==NULL)
{
perror("malloc");
exit(-1);
}
for(int i=0;i<=10;i++)
{
p[i] = i; //当i=10时越界访问
}
free(p);
p = NULL; //释放空间后,需要把p置空
}
对非动态开辟的内存使用free释放
void test()
{
int a =10;
int* p = &a;
free(p);
}
使用free释放一部分空间
void test()
{
int* p = (int*)malloc(sizeof(int));
p++;
free(p);
}
对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(sizeof(int));
free(p);
free(p); //重复释放
}
动态内存忘记释放
void test()
{
int* p = (int*)maollc(sizeof(int));
if(p==NULL)
{
perror("malloc");
exit(-1);
}
*p = 4;
}int main()
{
test();
}
5.柔性数组
什么是柔性数组?
- 结构中柔性数组成员前面必须有至少一个成员
- sizeof返回的结构体的大小不包括柔性数组的内存
- 包括柔性数组成员的结构用malloc()函数进行内存的分配,并且分配的内存应该大于结构的大小
typedef struct st
{int i;
int a[];
//不使用柔性数组
int* a;
//
}st;
int main()
{
int j = 0;
st* p = (st*)malloc(sizeof(st)+40*sizoef(int));
//不使用柔性数组
st* p = (st*)malloc(sizeof(st));
p->a = (int*)malloc(sizeof(int)*40);
//
p->i = 40;
for(j=0;j<40;j++)
{
p->a[j] = j;
}
free(p)
return 0;
}
柔数数组优点
- 方便内存释放
如果我们在程序中二次进行内存分配,我们释放结构体指针p指向的空间,但是释放内存时可能会忘记释放结构体成员p->a的空间,使用柔性数组直接释放p指向的空间即可
- 有利于增加访问速度
连续的内存有利于增加访问速度,也有益于减少内存碎片