有关动态内存管理的解析

目录

一、为什么要动态内存开辟

二、动态内存开辟的函数

一、malloc

二、free

二、 calloc

三、realloc

三、动态内存常见的错误

1、动态内存开辟出来的空间忘记释放

2、对同一块动态内存空间进行多次释放

3、使用free释放动态开辟的内存中的一部分

4、对非动态内存开辟的空间进行free

5、对动态开辟的空间进行越界访问

6、对空指针进行解引用操作

四、柔性数组


一、为什么要动态内存开辟

我们一般在创建变量的方式是:

int main()
{
	int x = 10;
	int str[5] = { 1,2,3,4,5 };
	return 0;
}

像这样,我们创建变量的大小是固定的,数组的长度在编译前必须指定大小, 无法在编译时开辟空间,这时候我们就可以试试动态内存开辟。


二、动态内存开辟的函数

一、malloc

1、函数声明

void* malloc(size_t size);

2、函数参数

size —— 要开辟的内存块的大小,以字节为单位。类型是size_t,就是无符号整形unsigned int。

它是用来向内存申请一块连续的空间,返回类型是void*的指针,即返回开辟出来的空间的地址,因为返回类型是void*,所以要强制类型转换成想要的类型。

但是这个不是百分百能开辟成功,也可能开辟失败,开辟失败的话,就会返回空指针NULL,所以,我们每次用malloc开辟空间后,使用这块空间前都要判断一下,看是不是空指针,如果不是就使用,如果是,就报错,并不再使用这块空间。

如果参数为0的话,即开辟的空间大小为0,这取决于编译器,在malloc的标准中是未定义的。

二、free

1、函数声明

void free (void* ptr);

2、函数参数

ptr —— 指向以前使用或分配的内存块的指针。

它是和malloc搭配使用的,malloc用来开辟空间,而free就用来释放这个空间,所谓有借有还再借不难嘛。

注意一定要搭配使用,及时释放掉这块空间,否则会导致内存泄漏的问题。

但也不是说只要两个都出现了就一定不会出bug,如果free的参数不是动态内存开辟出来的空间,那就会报错,如果是空指针的话,就不会做出反应。

3、举例

#include
#include

int main()
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);//输出结果为0 1 2 3 4
	}
	free(arr);
	arr = NULL;
	return 0;
}

二、 calloc

1、函数声明

void* calloc (size_t num, size_t size);

2、函数参数

num —— 要分配的元素数。

size —— 每个元素的大小

功能和malloc差不多,都是从堆上开辟空间的,不同的地方就是malloc开辟出来的空间里面的值是随机值,需要给它赋值,而calloc开辟出来的空间是直接初始化为0。

它和malloc一样也是需要和free搭配使用。

3、举例

#include
#include


int main()
{
	int* arr = (int*)calloc(sizeof(int), 5);
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);//输出0 0 0 0 0
	}
	free(arr);
	arr = NULL;
	return 0;
}

三、realloc

1、函数声明

void* realloc (void* ptr, size_t size);

2、函数参数

ptr —— 想要重新分配大小的内存空间的地址(指针)

size —— 想要重新分配多少空间的字节数。

这个函数是用来为地址重新分配空间的,如果参数是个空指针,那么它就会分配一个空间,并返回起始地址,可以和malloc或者calloc搭配使用,但是不要忘记释放掉这块空间。

另外,如果ptr是NULL,就相当于malloc(size),如果size是0,就相当于free(ptr)。

3、举例

#include
#include

int main()
{
	int* ptr = NULL;
	int* arr = (int*)realloc(ptr, 5 * sizeof(int));//ptr为空指针,这里相当于malloc(5 * sizeof(int))
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	ptr = arr;
	for (int i = 0; i < 5; i++)
	{
		ptr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", ptr[i]);
	}
	free(arr);
	arr = NULL;
	return 0;
}

三、动态内存常见的错误

1、动态内存开辟出来的空间忘记释放

开辟出来的空间一直不释放,会导致内存泄漏。

#include
#include

int main()
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	//未使用free进行释放
	return 0;
}

2、对同一块动态内存空间进行多次释放

#include
#include

int main() 
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	free(arr);
	free(arr);//重复free
	arr = NULL;
	return 0;
}

这时候编译器就会发现了这个arr没有初始化,里面是随机值,是已经被释放了的 ,再进行释放就会报错。

3、使用free释放动态开辟的内存中的一部分

就是说指针位置不能改变,如果想要遍历,可以再创建一个指针并赋值给这个指针。

#include
#include

int main()
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	arr++;//arr改变了位置
	free(arr);
	arr = NULL;
	return 0;
}

4、对非动态内存开辟的空间进行free

#include
#include

int main()
{
	int a = 10;
	int* pa = &a;
	free(pa);//非动态开辟的空间
	pa = NULL;
	return 0;
}

5、对动态开辟的空间进行越界访问

#include
#include

int main() 
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i <= 5; i++)
	{
		printf("%d ", arr[i]);//越界访问,打印出来的是个随机值
	}
	free(arr);
	arr = NULL;
	return 0;
}

6、对空指针进行解引用操作

#include
#include

int main() 
{
	int* arr = (int*)malloc(5 * sizeof(int));
	if (arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i <= 5; i++)
	{
		printf("%d ", arr[i]);//越界访问,打印出来的是个随机值
	}
	free(arr);
	arr = NULL;
	return 0;
}

四、柔性数组

柔性数组是在结构体中最后一个元素允许未知大小的数组,但它仅限于C99标准。

我们来看看一个包含柔性数组的结构体的大小

#include
#include


typedef struct array
{
	int x;
	int arr[];//柔性数组
}array;

int main()
{
	printf("%d\n", sizeof(array));//输出结果是4
	return 0;
}

从这里我们可以推断出,结构体大小是没有把柔性数组大小算进去的,因为在创建柔性数组的时候是没有指定大小的,需要使用动态内存开辟的函数来开辟空间,并且开辟的空间应该大于结构体大小。

包含柔性数组的结构体的使用:

#include
#include

typedef struct array
{
	int x;
	int arr[];//柔性数组
}array;

int main()
{
	int i = 0;
	array* ptr = (array*)malloc(sizeof(array) + 5 * sizeof(int));
	if (ptr == NULL)
	{
		perror("main()");
		return 1;
	}
	ptr->x = 100;
	for (int i = 0; i < 5; i++)
	{
		ptr->arr[i] = i;
	}
	printf("%d\n", ptr->x);
	for (int i = 0; i < 5 ;i++)
	{
		printf("%d ", ptr->arr[i]);
	}
	return 0;
}

这里注意,结构体中的柔性数组成员前面必须至少有一个其他成员。

另外,上面这个结构体也可以写成这样:

#include
#include

typedef struct array
{
	int x;
	int* arr;//柔性数组
}array;

int main()
{
	int i = 0;
	array* ptr = (array*)malloc(sizeof(array));
	if (ptr == NULL)
	{
		perror("main()");
		return 1;
	}
	ptr->x = 5;
	ptr->arr = (int*)malloc(ptr->x * sizeof(int));
	if (ptr->arr == NULL)
	{
		perror("main()");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		ptr->arr[i] = i;
	}
	printf("%d\n", ptr->x);
	for (int i = 0; i < 5 ;i++)
	{
		printf("%d ", ptr->arr[i]);
	}
	free(ptr->arr);
	ptr->arr = NULL;
	free(ptr);
	ptr = NULL;
	return 0;
}

 这样的话就没有第一个好,因为开辟空间分两次开辟,释放空间也需要分两次释放,这样显得第一种更方便释放内存,并且,开辟空间是连续开辟的,提高访问速度,如果分两次,那这两块空间就有可能不是连续的,会有内存碎片,虽然影响不是很大。

你可能感兴趣的:(c语言,开发语言)