动态内存管理
1.我们如果想获得一块可大可小,空间不够了可以增加,空间大了可以缩小
应该如何实现呐?
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
这样只能获得固定的空间,程序一跑空间就改变不了。
2.c语言为我们提供了一组库函数来实现这一目标:
- malloc
- calloc
- realloc
- free(用来释放开辟的空间)
关于malloc是如何申请内存的,推荐看这篇文章,写的超级棒
自己动手实现一个malloc内存分配器 - 码农的荒岛求生的文章 - 知乎
https://zhuanlan.zhihu.com/p/367060283
malloc
- 返回值malloc返回一个指向已分配空间的void指针,如果可用内存不足则返回NULL。
- 若要返回指向非void类型的指针,请对返回值使用类型强制转换。
- 参数是size_t单位是字节
- 如果参数为0这种行为,c语言未定义,尽量不要这么写
-
此时tmp指向一块开辟好的40个字节的空间
- 释放:由于malloc开辟的空间是在栈上的,所以只能程序员自己释放,否则只能程序结束后由操作系统释放(但是有些程序有可能是没日没夜的运行,不释放的话有可能会造成内存泄漏。)
完整示范:
#include <stdlib.h>
#include <string.h>
int main()
{
int *tmp=(int *)malloc(40);
//malloc有可能开辟失败,使用前一定要进行判断
//不能直接使用tmp,tmp可能是空指针,对空指针解引用会出现错误
if (tmp == NULL)
{
printf("%s", strerror(errno));
return 0;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(tmp + i) = i;
printf("%d ", *(tmp + i));
}
//释放
free(tmp);
tmp = NULL;
return 0;
}
calloc
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
realloc
realloc和前面的有点不一样
- 第一个参数必须是malloc\calloc出来的(必须是动态内存开辟出来的指针)即要调整的内存的地址
- size_t调整之后的新大小
- 情况一:内存看做一条长长的停车场,我们申请内存就是要找到一块停车位,释放内存就是把车开走让出停车位。只不过这个停车场比较特殊,我们不止可以停小汽车、也可以停占地面积很小的自行车以及占地面积很大的卡车。要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
- 情况2:原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。并且释放旧的空间,这样函数返回的是一个新的内存地址。
因此使用realloc时候一定注意是否原空间地址改变
#include <stdlib.h>
int main()
{
int *tmp=(int *)malloc(40);
int i = 0;
for (i = 0; i < 10; i++)
{
*(tmp + i) = i;
printf("%d ", *(tmp + i));
}
int* pp=(int *)realloc(tmp, 80);
//此时不能直接写成
//tmp = pp;
//万一realloc开辟失败返回一个空指针,那么原来的
//40个字节都找不到了
if (pp != NULL)
{
tmp = pp;
//使用
for (i = 0; i < 20; i++)
{
*(tmp + i) = i;
printf("%d ", *(tmp + i));
}
}
//释放
free(pp);
pp = NULL;
return 0;
}
realloc也可以开辟一块空间,如
#include <stdlib.h>
#include <string.h>
int main()
{
int *tmp= (int*)realloc(NULL, 40);
//这里也可以开辟一块空间
//效果和malloc一摸一样
if (tmp == NULL)
{
printf("%s", strerror(errno));
return 0;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(tmp + i) = i;
printf("%d ", *(tmp + i));
}
free(tmp);
tmp = NULL;
return 0;
}
free
这里参数是开辟的起始位置的地址
注意以下错误:
1.使用free释放一块动态开辟内存的一部分
//使用free释放一块动态开辟内存的一部分
int main()
int* p = (int* )malloc(40);
if (p == NULL)
printf("%s\n",
strerror(errno));
return 0;
}
int i=0;
//[1] [2] [3] [4] [5] [][] [][][]
for(i=0;i<5;i++)
{
*p=i+1;
p++;|
}
//释放
free(p);
p = NULL;
return 0;
}
最好不要让指向动态开辟的空间的起始位置的地址跑来跑去,否则释放不了,free只能释放起始位置的地址。想释放这块空间必须提供起始位置的地址
2 .对非动态开辟内存使用free释放
int main()
int arr[10] = { 1,2,3,4,5 };
int* p = arr;
//....
free(p);
p = NULL;
return 0;
这里p开辟的空间不在堆上不能释放
柔性数组
- C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#include <stdlib.h>
#include <string.h>
struct S
{
int n;
char C;
int arr[0];//柔性数组成员
};
int main()
{
// 8 + 40
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
if (ps == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
//使用
ps->n = 100;
ps->C = 'w';
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d\n", ps->arr[i]);
}
//调整arr数组的大小
struct S* ptr = (struct S*)realloc(ps,sizeof(struct S) + 20 * sizeof(int));
if (ptr == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
else
ps = ptr;
//使用
//释放
free(ps);
ps = NULL;
}
可以动态的管理一块数组空间,个人感觉这个很有用