1.为什么存在动态内存分配
int main()
{
int a = 0;
char arr[] = "abcde";
return 0;
}
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
- 而有时候我们我们需要的空间大小在程序运行起来才能知道,这时候就需要动态开辟的内存了
2.动态内存开辟的函数
1.malloc
- 由于malloc不清楚将来申请的空间的类型是什么,所以不能确定指针类型,那么就返回一个空指针
- 参数为申请的字节数
- 我们可以将malloc的返回值强制类型转换成我们想要的指针类型
int main()
{
char*p = (cahr*)malloc(10*sizeof(char));
if (p == NULL)
{
perror("malloc");
return 1;
}
free(p);
p = NULL;
}
2.calloc
- 参数分别为元素个数,每个元素大小
- 可以将动态开辟的空间初始化为0
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
free(p);
p = NULL;
}
3.realloc
int main()
{
int*p = (int*)malloc(sizeof(int) * 10);
if (p == NULL)
{
perror("calloc");
return 1;
}
int*str = (int*)realloc(p,sizeof(int)*20);
if (str == NULL)
{
perror("realloc");
return 1;
}
p = str;
free(p);
p = NULL;
return 0;
}
- 当所开辟的空间小了,用realloc来调整动态内存开辟的空间,使其变大
- 因为返回值可能为空指针,所以realloc的返回值不能直接用p来接收,需要先进行判断
- realloc在调整空间大小时有两种情况
- 情况一
- malloc申请的空间后面有足够大的空间用来调整空间,realloc会在空间后追加一块新的空间
- 情况二
-
- malloc申请的空间后面没有足够大的空间用来调整空间,realloc寻找一块新的空间供其开辟,并且把原来的数据拷贝到新的内容,返回新的空间的地址
- 由此得出realloc开辟的空间可能返回旧地址,也可能返回新地址,所以要将realloc返回的指针传给p
4.free
- 释放开辟动态内存
- 当free的参数传的是NULL时,free不会做任何事
3.常见的动态内存错误
1.对NULL解引用
void test()
{
int *p = (int *)malloc(sizeof(int)*4);
*p = 20;
free(p);
}
2.对动态内存开辟空间的越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
return;
}
for(i=0; i<=10; i++)
{
*(p+i) = i;
}
free(p);
}
3.对非动态内存开辟的空间进行free
void test()
{
int a = 10;
int *p = &a;
free(p);
}
4.使用free释放动态内存的一部分
void test()
{
int *p = (int *)malloc(10);
p++;
free(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 = 10;
}
}
int main()
{
test();
return 0;
}
- 此时p从函数出来时被回收,也就没人记得该内容的地址了,导致内存泄漏
4. 柔性数组
struct s
{
int a;
int arr[];
};
- 结构体中柔性数组前面必须至少有一个成员,否则不知道结构体大小多大,不能为它开辟空间
- sizeof计算结构体大小不包括柔性数组大小
- 包含柔性数组的结构体要用malloc动态开辟内存空间,并且要大于结构体大小来适应柔性数组的预期大小
1. 使用柔性数组动态内存开辟与不使用柔性数组动态内存开辟
struct s
{
int a;
int arr[];
};
int main()
{
struct s*p = (struct s*)malloc(sizeof(struct s)+sizeof(int)*10);
if (p == NULL)
{
perror("malloc");
return 1;
}
p->a = 100;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p->arr+i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p->arr+i));
}
printf("\n");
struct s* str = (struct s*)realloc(p, sizeof(struct s) + sizeof(int) * 20);
if (str == NULL)
{
perror("str");
return 1;
}
p = str;
for (i = 10; i < 20; i++)
{
*(p->arr + i) = i;
}
for (i = 0; i < 20; i++)
{
printf("%d ", *(p->arr + i));
}
free(p);
p = NULL;
return 0;
}
struct s
{
int a;
int *arr;
};
int main()
{
struct s* p = (struct s*)malloc(sizeof(struct s));
int*pc = (int*)malloc(sizeof(int) * 10);
if (p == NULL)
{
perror("p:malloc");
return 1;
}
if (pc == NULL)
{
perror("pc:malloc");
return 1;
}
p ->arr = pc;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p->arr + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p->arr + i));
}
int*str = (int*)realloc(p->arr, 20 * sizeof(int));
if (str == NULL)
{
return 1;
}
p->arr = str;
printf("\n");
for (i = 10; i < 20; i++)
{
*(p->arr + i) = i;
}
for (i = 0; i < 20; i++)
{
printf("%d ", *(p->arr + i));
}
free(p->arr);
p->arr = NULL;
free(p);
p = NULL;
return 0;
}
2.使用柔性数组的优点
- 使用柔性数组只是用了一次malloc,free,不使用柔性数组使用了两次malloc,free
- 在堆区上进行动态内存开辟时,开辟的空间并不是一块紧接着一块,它们之间的空隙成为内存碎片,由于内存碎片所占空间较小,被使用的机会也会很少,这就导致了空间的浪费,内存使用率下降,所以要尽量少的使用malloc
- 使用更多次的malloc,free,空间维护难度加大,更容易出错