一.为什么存在动态内存分配
//局部变量
int a = 2;//在栈区开辟4个字节
char arr[10] = {0};//在栈区开辟10个字节的连续的空间
上述的空间开辟方式有两个特点:
- 开辟空间大小是固定的
- 数组在声明的时候必须指定数组的长度,其所需要的内存在编译时分配
不过对于空间的开辟,有时我们需要想随便开辟多大空间的时候,就需要用到动态内存分配
二.动态内存函数的介绍
头文件均为 <stdlib.h>
malloc(开辟动态内存空间)
- 开辟空间成功时,返回指向函数分配的内存块的指针。
- 开辟空间失败时,则返回一个空指针(NULL)。因此malloc函数的返回值一定要做检查
- 返回值的类型是void* ,具体情况下进行强制类型转换
-
malloc函数和free函数应一块使用
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
printf("%d\n",*(p + i));
}
}
return 0;
}
free(释放动态内存空间)
- 参数(ptr)为 指向以前使用 或 分配的内存块的指针
- 使用时,得有free函数应一块使用
-
如果参数 ptr 指向的空间不是动态开辟的,则free函数的行为是未定义的
-
如果参数ptr为NULL指针,则free函数什么都不会做
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*) malloc(10*sizeof(int));//void*强制类型转换为int*
//或int* p = (int*)calloc(40);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
printf("%d\n",*(p + i));
}
}
free(p);
p = NULL;
return 0;
}
注意:
//当动态申请的空间不再使用的时候,就应该还给操作系统
//虽然程序结束后由于生命周期而自动会还给操作系统,但当我们后续还有,这回导致这白白浪费
//虽然free(p)释放了p的空间后,但p仍能找到这个空间,这是具有潜在危险的,所以我们可以直接将其置空
calloc(开辟动态内存空间)
- 返回值是指向开辟的空间的指针
- 开辟空间失败时,则返回一个空指针(NULL)
- calloc与malloc参数用法不同;calloc会把空间的每个字节初始化为0,而malloc不会初始化
-
使用时,得有free函数应一块使用
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*)calloc(10,sizeof(int));//void*强制类型转换为int*
//或int* p = (int*)calloc(10,4);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
printf("%d\n",*(p + i));
}
}
free(p);
p = NULL;
return 0;
}
//0 0 0 0 0 0 0 0 0 0
realloc(调整动态内存的大小)
realloc函数能让动态内存管理更加灵活,在发现过去申请的内存空间过小或过大,可以使用realloc函数进行大小的调整
- 如果p指向的空间后有足够的内存空间可以追加,则直接追加,返回p
- 如果p指向的空间后没有足够的内存空间可以追加,则realloc函数会重找一个能满足需求的内存空间,并将原来内存中的数据拷贝过来,谁放掉旧的空间,最后返回新的内存空间
- realloc函数调整失败后会返回一个空指针,不能拿p直接接收realloc,得用一个新的变量来接收返回值
-
使用时,得有free函数应一块使用
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*)malloc(20);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<5;i++)
{
*(p + i) = i;
printf("%d ",*(p + i));
}
}
int* p2 = (int*)realloc(p,40);
int i = 0;
for(i = 5;i < 10;i++);
{
printf("%d ",*(p2 + i));
}
//free(p);
//p = NULL;
return 0;
}
注意:
//当realloc第一个参数为NULL时,可以相当于malloc
int* p = (int*)realloc(NULL, 40);
三.常见的动态内存错误
对空指针的解引用操作
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);
//未对失败的情况下判定
//万一malloc失败了,p被赋值为NULL
//而下面的操作,都成了非法操作
int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
对动态内存开辟空间的越界访问
开辟动态内存空间后,后面进行操作时却超出这个空间的大小
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<20;i++)// 进行越界访问了
{
*(p + i) = i;
}
}
return 0;
}
对非动态开辟内存使用free函数释放
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int a = 10;//a在栈区
int *p = &a;
*p = 20;
free(p);//free作用在堆区,强行乱指会造成程序崩溃
return 0;
}
使用free释放一块动态开辟内存的一部分
由于使p的地址发生改变时,释放的不再是原来的初始地址
#include <stdio.h>
#include <stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;
}
free(p);
p = NULL;
return 0;
}
对一块动态内存进行多次释放
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)// 进行越界访问了
{
*(p + i) = i;
}
}
free(p);
free(p);
p = NULL;
return 0;
}
- 可以在一个free后紧跟着将其置空,后面再出现时不会有影响
free(p);
p = NULL;
free(p);
动态开辟内存忘记释放(内存泄露)
#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if(p != NULL)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄露
————动态开辟的空间一定要进行正确的释放
注意:
动态开辟的空间有2种回收方式:主动free , 程序结束
四.柔性数组
1.定义
在 C99 中,结构体中的最后一个元素允许是未知大小的数组,即柔性数组
struct a{
int b;
int arr[];//大小是未知的
};
//或
struct a{
int b;
int arr[0];//大小是未知的
};
2.特点
-
结构体中的 柔性数组成员 的 前面 必须至少有一个其它成员
- sizeof 计算这种结构体的大小 不包含柔性数组成员的
-
包含柔性数组成员的结构体用 malloc 函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
#include<stdio.h>
#include<stdlib.h>
struct A{
int b;
int arr[];//大小是未知的
};
int main()
{
//期待arr的大小是10个整形
//结构体的初始化
struct A* ps = (struct A*)malloc(sizeof(struct A ) + 10*sizeof(int));//前4个字节是给b的,后40个字节是给arr的
//由于后面的是通过malloc开辟的动态内存,大小是可以调整的
printf("%d",sizeof(ps));
free(ps);
ps = NULL;
return 0;
}
//4