【C语言】动态内存函数(malloc、calloc、realloc和free)


1.为什么存在动态内存分配

int a = 10;
int arr[10] = { 0 };

没有对比,没有伤害。在声明变量的时候,其类型早已说明向内存申请多少字节,在创建数组时,必须指定数组的长度,所以空间大小是固定的,不能变大变小,有局限性。所以就有一种方式向内存申请空间,不够变多,太多变少,就是动态内存分配。
那如何进行动态内存分配?就要使用动态内存函数。


2.动态内存函数

内存空间分为栈区、堆区和静态区。栈区主要是局部变量和函数形参等的空间;堆区主要是动态内存分配的空间;静态区主要是静态变量和全局变量的空间。

2.1malloc

在这里插入图片描述

注意

  1. malloc分配的内存块并没有初始化,可能有一下不确定的值。
  2. malloc可能申请空间失败,这时会返回NULL。
  3. malloc的参数是0时,返回值取决于编译器。
  4. malloc的返回值类型是void*,需要进行强制类型转换才能赋值。
  5. malloc申请的空间是连续的。

例子

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);//记得强制类型转换
	if (NULL == p)//记得判断是否为NULL
	{
		perror("malloc");//将错误信息打印出来
		return 1;//顺便返回
	}
	//存放1~10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

但是上面的代码存在隐患。向别人借东西岂有不还的道理,向内存申请空间,如果用完不还给操作系统,就会造成浪费,尽管在程序结束后,空间会自动还给操作系统,但如果这个程序一直执行呢?所以我们就要用free函数释放申请的空间。

2.2free

在这里插入图片描述

注意

  1. free只能释放动态内存的空间。
  2. free的参数是NULL就不执行任何操作。
  3. free只是释放指针指向的空间,指针任然指向那片空间,只是空间早已还给操作系统。
    所以通常还要把指针置为NULL。

例子

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	//存放1~10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

答案
在这里插入图片描述

2.3calloc

在这里插入图片描述
注意

  1. calloc的作用和malloc相同,都是申请空间。但calloc更细致,将内存块分为元素大小和元素个数。
  2. calloc和malloc最大的区别在于calloc申请空间成功后会将空间初始化为0,而malloc不会。
  3. calloc的其他注意事项与malloc相同。

例子

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

结果
在这里插入图片描述

2.4realloc

在这里插入图片描述

注意

  1. realloc的第一个参数如果是NULL,其作用就像malloc一样,重新分配一个size的空间,并返回其起始地址。
  2. 重新分配空间时,当旧空间后面有足够大的空间时,可以容纳下重新分配的空间,就返回原先的指针(指向旧空间的其实地址);但relloc可能返回新的地址,原因是旧空间后面没有足够的空间来扩展,只能在堆区重新寻找一个更大的空间并返回其地址。
  3. 原先空间的值会自动被拷贝过来,不用担心旧空间丢了,数据找不到了,并且会把旧的空间释放掉,同时返回新的空间的地址。
  4. 不能用原来的指针来接收,万一申请空间失败,realloc会返回NULL,这样就失去那片空间的地址,不能找到那片空间,那就不能释放,可能导致内存泄漏。
  5. realloc分配内存失败,返回NULL,至于原先旧空间没有被释放看不同的标准,在C90旧空间并没有被释放,也可能释放,在C99中,旧空间并没有被释放。
    在这里插入图片描述
    例子
int main()
{
	int* p = (int*)malloc(5, sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
	}
	//当你觉得5个整形的空间不够时,你要申请10个整形的空间、
	int* pp = (int*)realloc(p, sizeof(int) * 10);
	if (pp != NULL)
	{
		p = pp;
		pp = NULL;
	}
	free(p);
	p = NULL;
	return 0;
}

3.常见的动态内存错误

3.1对空指针的解引用操作

int main()
{
	int* p = (int*)calloc(10,sizeof(int));//没有想到可能申请空间失败返回NULL
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
}

3.2对动态内存开辟的越界访问

int main()
{
	int* p = (int*)malloc(100);//malloc的参数单位是字节,也就是说申请了100个字节(25个整形)的空间
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 100; i++)//只申请了25个整形的空间,却要访问100个,属于越界访问
	{
		*(p + i) = 0;
	}
	free(p);
	p = NULL;
	return 0;
}

3.3对非动态开辟内存使用free释放

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

前面学习free的时候提到free的参数指针,指向动态内存函数开辟的空间,是在堆区上进行操作的,而非动态内存开辟的空间是不能用free释放的。

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

int main()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;//p不再指向动态内存开辟的空间的起始地址
	}
	free(p);
	p = NULL;
	return 0;
}

3.5对同一块动态内存多次释放

int main()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	//……
	//……
	free(p);
	p = NULL;
	return 0;
}

早已释放掉的内存还想再释放一次,像买的房子卖掉后想再卖一次,没有这样的道理。前面提到如果p为NULL,那么free§就不执行任何操作。所以早在第一次使用free时,就把p置为NULL,那么后面使用free就不会导致错误了。所以记得free§和p=NULL记得配套使用。

3.6动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		perror("malloc");
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
}
int main()
{
	test();
	return 0;
}

在test函数申请的空间没有释放,回到主函数,没有找到这片空间的方法,想释放这片空间都没办法,因为指针已被销毁。
如何解决这个问题?可以将指针返回主函数,同时写好注释,提醒在主函数利用完这片空间后释放。但最稳妥的办法就是malloc、calloc和realloc与free配套使用。

4.练习题

指出下面面试题的错误
1.

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

str传给p的时候,p是str的临时拷贝,有自己独立的空间,当GetMemory函数内部申请了空间后,地址放在p中,str依然是空指针,当GetMemory函数销毁后,strcpy拷贝时形成非法访问内存。正确的做法是返回指针。
注意
printf(str)是正确的。原因:printf(“hello”)中hello是常量字符串,放是其实是首字符的地址,而char*str = "hello"中放在str中的也是首字符的地址,所以printf(“hello”)==printf§。

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

GetMemory函数销毁后,这块空间还给操作系统,但p还是指向这块空间,再使用这块空间就形成非法访问。怎么改?将p[]改为*p,hello world就是常量字符串,是内存的一块空间,不会随着函数的销毁而销毁。或者用static修饰,hello world也不会被销毁。

void GetMemory(char **p, int num)
{
	*p = (char *)malloc(num);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

没有对动态分配内存释放,属于内存泄漏。怎么改?加上free(str);str = NULL。

void Test(void)
{
	char *str = (char *) malloc(100);
	strcpy(str, "hello");
	free(str);
	if(str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

释放内存后,没有将指针置为NULL,指针还是指向这块空间,所以再对指针使用属于非法访问。怎么改?在free(str);后面加上str=NULL。


猜你喜欢

转载自blog.csdn.net/Zhuang_N/article/details/128856114