C语言——动态内存管理:

上篇文章用C语言实现通讯录,在实现通讯录的过程中,对于通讯录中可以存储保存联系人信息的结构体的数量是固定的,这会导致出现需要存储的数量大于设置的数量的问题。为了结果这个问题,让变量在内存中的大小的调节更为灵活,本文将引入动态内存管理。

1. 为什么要引入动态内存管理:

正如上面所说,目前对于内存空间的开辟方式一共两种:一种是创建一个变量:

int a = 0;

第二种是创建一个数组:

int arr[10] = {0};

不过对于这两种内存开辟的方式存在一定的局限性:

C语言——动态内存管理:_第1张图片

 为了解决上面的问题,所以引入动态内存管理:

2. 动态内存函数的介绍:

2.1  malloc和free函数:

   对于malloc函数的特点,由下面的一张图给出:

C语言——动态内存管理:_第2张图片

 mallco函数的参数只有一个,这个参数代表想要开辟空间的大小,单位是字节。对于malloc函数的使用,下面给出一个例子:用malloc函数向内存申请40个字节大小的空间:
 

int main()
{
	int* p = (int*)malloc(40);
	return 0;
}

上面说到,malloc函数会存在开辟失败的情况,所以,为了后续正常使用malloc函数,最好在开辟完内存空间后,检查一下是否开辟成功:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	return 0;
}

开辟空间后,下面对开辟的空间的地址进行打印:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%p\n", *(p + i));
	}
	return 0;
}

结果如下:

C语言——动态内存管理:_第3张图片

malloc函数申请的空间的地址,是随机值 ,并且malloc在申请到空间后,会直接返回这块空间的初始地址,不会对空间初始化。并且malloc函数申请的空间在程序退出时会还给操作系统,当程序不退出,动态申请的内存不会主动释放。为了能够合理的释放malloc函数申请的内存空间,下面引入free函数:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%p\n", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

之所以在free释放内存空间后将p赋值NULL,是为了避免指针p变成野指针。 并且,对于free释放的空间,只能是动态开辟的空间,例如下面举一个错误的例子:用free释放非动态开辟空间:

int main()
{	
    int i = 0;
	int* p = &i;
	free(p);
	p = NULL;
	return 0;
}

另外,如果在free中传入空指针NULL,则函数什么也不做。可以视为空指令。

2.2 calloc函数:

对于calloc函数,他的功能和malloc函数相比则更为丰富:

C语言——动态内存管理:_第4张图片

calloc函数的参数有两个,第一个参数用来填写想要开辟空间的数量,第二个参数是用来填写开辟的空间的每个元素的大小,例如,用malloc函数开辟十个整型空间:

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

打印结果如下:

C语言——动态内存管理:_第5张图片

2.3 realloc函数: 

realloc函数的参数同样也是两种:

C语言——动态内存管理:_第6张图片

 其中,第一个参数表示被调整空间的起始地址,需要注意的时,被调整的空间是已经用malloc、realloc、calloc创建好的。第二个参数表示新的空间的大小。对于第一个参数,如果传递一个空指针,则realloc函数的作用 = malloc函数。 

例如下面的例子,用realloc函数对malloc函数申请的空间进行扩大到80字节:

int main()
{
	int* p = (int*)malloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	realloc(p, 80);
	return 0;
}

不过,对于realloc函数的返回值有两种不同的情况:

1. 当后面的空间足够时,realloc函数返回被调整空间的起始地址

2.当后面的空间不足够时,会开辟一个新的空间,并返回这个新的空间的地址。对于这种情况,realloc函数会分四步完成: 1. 开辟新的空间 2. 将旧的空间中的数据拷贝到新的空间中 3.释放旧的空间  4. 返回新的空间的地址

对于realloc返回值的接收并不能由指针p直接接受,因为,如果realloc函数创建空间失败,则会返回一个空值,此时如果用p进行接收,会造成不但不能正常开辟空间,还会造成原来被malloc开辟的空间因为p变成了野指针而无效化。

所以,对于realloc函数返回值的接受,应该创建另一个指针来检验返回值是否为空,不为空再赋给p:

int main()
{
	int* p = (int*)malloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int* ptr = realloc(p, 40);
	if (ptr != NULL)
	{
		p = ptr;
	}
	return 0;
}

为了更好的展示realloc函数的作用,给出下面一个情景:先利用malloc开辟40字节大小的内存空间,再将这些空间赋值1到10,再用realloc函数增加80字节的空间,即:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = 1+i;
	}
	int* ptr = realloc(p, 40);
	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}

free(p);
free(ptr);
	return 0;
}

打印结果为:

C语言——动态内存管理:_第7张图片

 

 3. 常见的动态内存错误:

3.1 对NULL指针进行解引用操作:

上面对于动态内存函数开辟空间时提到过,动态内存函数又可能出现开辟空间失败的情况,一旦开辟失败并且不对返回值进行检测,极有可能造成对NULL指针进行解引用操作的错误。

3.2 对动态开辟空间的越界访问:

例如下面的情况:

int main()
{
	int* p = malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}
free(p);
	return 0;
}

在代码的开头利用malloc函数向内存申请了40个字节,也就是10个整形大小的空间,但是在下面对这块空间进行访问访问时,访问的大小超过了申请的大小,造成了越界访问。

3.3 对非动态开辟内存使用free函数:

对非动态开辟内存使用free函数的情况在上面进行了说明,这里不再解释。

3.4 使用free释放一块动态开辟内存的一部分:

例如下面的例子:

int main()
{
	int* p = malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}
	free(p);
	return 0;
}

还是利用malloc函数开辟10个整型大小的动态空间,但是进行释放时,指释放了部分的空间,此时程序会运行错误。

C语言——动态内存管理:_第8张图片

 所以,如果需要访问动态内存的内容且需要地址变化时,最好再创建一个指针,利用这个指针代替创建动态内存使用的指针,避免更改原指针,导致发生上述错误。

3.5 对同一块内存多次释放:

int main()
{
	int* p =(int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
	}
	free(p);
	free(p);
	return 0;
}

例如上面给出的代码中,连续两次释放指针p。此时会造成程序异常:

C语言——动态内存管理:_第9张图片

对于上述情况,如果需要对同一个指针多次释放,则在释放一次后,将指针初始化为NULL即可。 

3.6 动态内存开辟忘记释放:

例如下面给出的代码:

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
  {
*p = 20;
  }
}

int main()
{
test();
while(1);
return 0;
}

用于存放动态空间地址的指针p在函数结束后自动销毁,但是下面while(1)却是一个死循环,此时程序不结束,动态内存空间不能及时释放。

4.文章思维导图如下:

C语言——动态内存管理:_第10张图片

 

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