内存空间的分配和布局:

之前学过的开辟空间的方式都是固定了空间的大小的,开辟的空间在编译时分配,为了可以开辟任意大小的空间,我们使用动态的内存分配

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.柔性数组开辟的空间地址是连续的,不容易导致产生内存碎片