- 普通变量创建开辟空间大小是固定的
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配(
柔性数组除外,后文会写到
)在内存中如图
所以就有了动态内存
动态内存函数
1)malloc
void* malloc( size_t size );
size是分配未初始化储存的字节大小
开辟成功,则返回这个开辟的空间的指针
。开辟失败,则返回NULL
。- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));//注意malloc的强转类型
if(NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//避免野指针
2)calloc
void* calloc( size_t num, size_t size );
num是数组元素个数 size是元素类型大小
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节全初始化为0
int *p = calloc(10, sizeof(int));
3)realloc
void *realloc( void *ptr, size_t new_size );
ptr为前面malloc、calloc开辟空间的地址
size为新空间的大小(注意,是总大小不是新开辟的空间大小
)
- 返回值为调整之后的内存起始位置。
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间
realloc在调整内存空间的场景:
- 原有空间之后没有足够大的空间,
函数会找一块够用的空间,同时把原来的数据copy下来,返回新开辟空间的地址,同时free掉原空间
- 原来的空间后面有足够的空间 紧接着直接开辟新空间
如果无空间可增加 则返回空指针,所以下面的代码是错误的
- 当没有空间时原来malloc开辟的空间的数据也会丢失
所以需要中间变量来确定realloc是否为空指针后再赋予p
//错误代码
p=realloc(...) //p为malloc开开辟的空间地址
4)free
void free( void* ptr );
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做
- free只能free堆区
动态内存常见错误
1)对NULL指针的解引用操作
注意:
局部变量未初始化为随机值,不能解引用
//错误代码
void inside()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//当p为空指针,出现问题
)
2)对动态开辟空间的越界访问
逻辑错误
//错误代码
int *p = (int *)malloc(10*sizeof(int));
for(int i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
3)对非动态开辟内存使用free释放
int a = 10;
栈区开辟空间
//错误代码
int a = 10;
int *p = &a;
free(p);//程序会崩溃
4)使用free释放一块动态开辟内存的一部分
p不再指向动态内存的起始位置
//错误代码
int *p = (int *)malloc(100);
p++;
free(p);//p++之前的空间将无法被释放
5)对同一块动态内存多次释放
详细剖析参考:为什么重复free()比内存泄漏危害更大
解决办法:free()后将p置为空指针
p=NULL;
//错误代码
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放,程序崩溃
6)动态开辟内存忘记释放(内存泄漏)
忘记释放不再使用的动态开辟的空间会造成内存泄漏
//错误代码
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;//没有free!!
}
>>四道题充分体会动态内存错误<<
C99柔性数组
在不同编译器中的两种写法
typedef struct flexible_array
{
int i;
int a[0];//柔性数组成员
}flexible_array;
typedef struct flexible_array
{
int i;
int a[];//柔性数组成员
}flexible_array;
为柔性数组开辟空间与使用
int i=0;
flexible_array *p = (flexible_array*)malloc(sizeof(flexible_array)+100*sizeof(int));
//如要继续开辟空间使用realloc即可
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
与结构体中的普通数组开辟空间比较
//普通数组
typedef struct normal_a
{
int i;
int *p_a;
}normal_a;
normal_a *p = malloc(sizeof(normal_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
柔性数组优势
- 方便内存释放
- 有利于访问速度(
连续的内存有益于提高访问速度,也有益于减少内存碎片
)
更深入理解 请参见陈皓大佬的>>C语言结构体里的成员数组和指针<<