内存空间的分配和布局:
之前学过的开辟空间的方式都是固定了空间的大小的,开辟的空间在编译时分配,为了可以开辟任意大小的空间,我们使用动态的内存分配
1.关于动态的内存分配的函数:
1.malloc函数:
void* malloc(size_t size)
这个函数用于向内存申请一块空间,它的参数size是向内存申请的空间的大小(单位:字节),返回的是所申请的空间的首地址,接收时需要对其进行强制类型转换。但是malloc函数向内存申请的空间大小是有限的,一旦申请失败,它会返回空指针NULL。所以使用malloc函数时需要对其返回值有效性进行检查。
2.free函数:
void free( void* memblock )
free函数用来释放malloc申请的动态内存,free不能用来释放别的空间,这种行为属于未定义。其次,要是free的参数是空指针,那么free函数相当于什么都没做。最后,free释放完空间后,原来指向动态内存的指针需要置为空指针,否则它就变成一个野指针。
3.calloc函数:
void* calloc( size_t num, size_t size );
这个函数也用于向内存申请一块空间,它的第一个参数是申请空间有几个元素,第二个参数是每个元素所占空间的大小,单位是字节。但这个函数与malloc函数也有区别,它会将每个字节初始化为0后再返回。
4.realloc函数:
void* realloc(void* memblock,size_t size)
这个函数用于追加或者减少原来申请的动态内存空间,这个函数第一个参数是需要延长/减少的原动态空间的地址,第二个参数是延长/减少后的新内存空间的大小。它的返回值是这块延长/减少后空间的地址。
注意:
1.如果旧空间后有足够的空间去申请一块所需新的空间,则返回原空间的地址。
2.如果旧空间后的空间不足以满足所需要新空间的大小,则realloc会另外开辟一块新的空间,此时返回的是新空间的地址,并且将原空间的内容拷贝过来同时释放原空间。
3.鉴于realloc返回的地址不一定为原来申请空间的地址,并且realloc函数可能会申请新空间失败,所以得用新的指针接收realloc的返回值。
其中 int* p = realloc(NULL,100);等价于int* p =malloc(100);
2.柔性数组
结构体最后一个成员变量如果是数组,并且这个数组前至少有一个成员变量,那么这个数组可以不标明长度。即这个数组没有占用内存。如下图所示的结构体大小仅为int类型的大小4。
struct S
{
int n;
int arr[0];//sizeof(struct S)==4;
};
使用柔性数组,我们可以开辟任意长度的数组,并且在开辟完后还可以对数组的大小进行修改
struct S
{
int n;
int arr[];//或者int arr[0];
};
int main()
{
struct S* s = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
//开辟5个整形大小的空间
struct S* ptr = (struct S*)realloc(s, sizeof(struct S)+10 * sizeof(int));
//增加5个整形大小的空间
if (ptr != NULL)
{
s = ptr;
}
free(s);
s = NULL;
return 0;
}
当然,我们也可以选择别的方式进行动态内存的开辟。类似于上面的柔性数组,我们可以创造一个结构体,里面放一个指针可以指向动态内存的空间。
struct S
{
int n;
int* arr;
};
int main()
{
struct S* p = (struct S*)malloc(sizeof(struct S));
p->arr = (int*)malloc(5*sizeof(int));
//开辟五个整形大小的空间
//....使用
free(p->arr);
p->arr = NULL;
free(p);
p = NULL;
return 0;
}
两种方法都是创造动态内存的方法,那使用动态内存的好处有:
1.柔性数组只需要使用一次malloc,减少了free的次数,避免犯错
2.柔性数组开辟的空间地址是连续的,不容易导致产生内存碎片