原创

C语言动态内存分配知识点归纳

温馨提示:
本文最后更新于 2022年06月17日,已超过 9 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

动态内存分配

动态内存分配的意义

在计算机的存储过程中存在两种内存,一种是静态内存,一种是动态内存,这两种内存之间存在着很大的差别

静态内存

静态内存所占用的空间对于编译器来说是可预计的,静态内存在使用的时候直接声明就好了,并且静态内存使用的是栈上的空间,不需要由编程者自己来申请。

动态内存

反之,动态内存使用的时候需要由编程人员自己申请,对于编译器来说,动态内存的大小完全取决于编程者,编译器不能预计,并且动态内存使用的空间是存储在堆上的。

简单点来说明

# include<stdio.h>
int main()
{
   
	char array[10] = "cyuyan";
	char *string = "language";
	printf("%s\n", *(&array));
	printf("%s\n", string);
	*(&array) = "language";
	string = "cyuyan";
	printf("%s\n", *(&array));
	printf("%s\n", string);
	return 0;
}

这个代码有问题吗?一看是不是没问题,但是运行之后就会发现存在很问题
在这里插入图片描述
E0137表达式必须是可修改的左值 动态内存 8
提示我们有一个值是不能被修改的,而行号是8,这就说明了第八行是不能被修改的,把第八行注释了来运行一下看看
在这里插入图片描述
代码正常的运行,发现数组里面的那个字符串是不能被更改的,而指针对应的字符串是可以被更改的,那为什么那里就不能被更改呢,这就涉及到了静态内存和动态内存的特点,数组是我们一开始就给定出来的了能存10个字符,这个内存是固定不变的,所以就是静态内存,而下面的指针指向的是一个字符串的首地址,指针他可以指向任何数据的地址,而这里正好指向了这个字符串,所以就打印出来了这个字符串,下面的修改只是更改了指针所指向的数据,所以那块数据其实是不固定的,所以再我们看来就实现了更改。从这里面就可以看出静态内存不能实现数据的修改,而动态内存可以实现数据的修改。在实际应用过程中,很显然在大多数情况下我们并不知道我们要存储多少数据,如果向内存申请了很多的空间,那就会造成浪费,如果申请少了又不能存下数据,这时C语言就给出来动态内存申请的方法。

动态内存分配的具体实现

malloc函数的使用

  • malloc函数的原型
void *malloc(unsigned int size)

形参的意思是传入的值为一个无符号的数,函数的返回值为一个不知道具体类型的指针,这样的返回值也使得再申请内存的时候可以用不同的类型来接收函数的返回值

  • 具体的使用方法
# include<stdio.h>
# include<stdlib.h>
void InputNum()
{
   
	int* array = (int*)malloc((sizeof(int)) * 11);
	for (int i = 0; i <= 10; i++)
	{
   
		array[i] = i;
		printf("%d ", array[i]);
	}
}
int main()
{
   
	InputNum();
	return 0;
}

在这里插入图片描述
申请了11个空间来存储0—10这几个数,当然我们还可以把11写成n,把10也写成n,这样就实现了动态的申请内存。但是我们是不是忘了一个问题,如果我们内存开辟失败呢,比如计算机的内存已经很少了,但是我们又申请了很多的空间,那是不是就会造成申请失败啊,C语言也给出来如果申请失败的话,返回一个空指针,那我们的代码就可以这样来优化

# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
void InputNum()
{
   
	int* array = (int*)malloc((sizeof(int)) * 11);
	if (array == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	for (int i = 0; i <= 10; i++)
	{
   
		array[i] = i;
		printf("%d ", array[i]);
	}
}
int main()
{
   
	InputNum();
	return 0;
}

如果是空指针的话就强行退出程序

calloc函数的使用

  • calloc函数的原型
void *calloc(unsigned n,unsigned size);

其实malloc函数和calloc函数都是可以用来申请内存的,那为什么要有两个呢?从中看他和malloc函数的区别在于多了一个参数,他的形参的意思是,传入一个无符号的数和一个无符号的数表示大小,意思就是我们要申请几个这样的数,我们可以来看看他们运行起来的区别

# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
void InputNum()
{
   
	int* array = (int*)malloc((sizeof(int)) * 11);
	if (array == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("array:");
	for (int i = 0; i <= 10; i++)
	{
   
		array[i] = i;
		printf("%d ", array[i]);
	}
	printf("\n");
	//calloc函数
	int* arr = calloc(11, sizeof(int));
	printf("arr:");
	if (arr == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	for (int j = 0; j <= 10; j++)
	{
   
		arr[j] = j;
		printf("%d ", arr[j]);
	}
}
int main()
{
   
	InputNum();
	return 0;
}

在这里插入图片描述
可以看出来好像没有区别,那我们再看看不给申请出来的内存赋值呢?

# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
void InputNum()
{
   
	int* array = (int*)malloc((sizeof(int)) * 11);
	if (array == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("array:");
	for (int i = 0; i <= 10; i++)
	{
   
		//array[i] = i;
		printf("%d ", array[i]);
	}
	printf("\n");
	//calloc函数
	int* arr = calloc(11, sizeof(int));
	printf("arr:");
	if (arr == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	for (int j = 0; j <= 10; j++)
	{
   
		//arr[j] = j;
		printf("%d ", arr[j]);
	}
}
int main()
{
   
	InputNum();
	return 0;
}

在这里插入图片描述
当我们将赋值操作给注释了就出现了这种情况,这些是啥啊,是不是乱码呢,其实不是,array其实是我们申请的内存但是没给他具体的值以为那个是地址,所以就会打印出当时指针所指向的那个值,而arr就没有出现这个情况,我们能发现出现的都0,是因为当我们使用calloc申请内存的时候就自动初始化了内存,初始值为0,可以看出,realloc函数不会自动初始化内存,而calloc函数会自动初始化内存,这两种方法各有好处,具体就要看编写者的需求了。

realloc函数的使用

  • realloc函数的原型
void *realloc(void *mem_address, unsigned int newsize);

除了上述这两种申请内存之外,其实还不够,为了满足更复杂的场景,出现了realloc函数,主要功能是扩充内存,里面的形参的意思是传入一个不知道具体类型的指针,传入一个无符号整型新的内存大小,我们来看看具体实现

# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
void InputNum()
{
   
	int* array = (int*)malloc((sizeof(int)) * 11);
	if (array == NULL)
	{
   
		printf("%s", strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("array:");
	for (int i = 0; i <= 10; i++)
	{
   
		array[i] = i;
		printf("%d ", array[i]);
	}
	printf("\n");
	int* arr2 = (int*)realloc(array, sizeof(int) * 20);
	for (int k = 0; k <= 19; k++)
	{
   
		printf("%d ", arr2[k]);
	}
int main()
{
   
	InputNum();
	return 0;
}

在这里插入图片描述
可以看到,新申请出来了20个空间,但是我们并没有赋值,为什么会和上面的值一言呢,其实realloc函数是将上面的数据拷贝到了下面,然后再申请空间,这就是realloc函数的特点。学习了这三个申请内存的方法,我们也应该会自己申请了吧。但是有没有发现一个问题,申请出来的内存最后都去了哪里呢,其实如果我们这样申请内存的话,会造成很大的问题,因为我们申请的内存,只申请不还回去,这就相当于我们去借钱,借了不还,那钱迟早会被借光的,内存也是这样,只申请不还回去的话,迟早会被我们借光的,借光之后就会导致计算机死机,甚至造成内存溢出的问题。那我们是如何处理这样的问题呢?

动态内存分配中需要注意的问题

  • 申请了的内存申请完毕后就要还回去
    让我们来看看如果我们不还回去的话会出现什么问题
# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
int main()
{
   
	while (1)
	{
   
		int* p = (int*)malloc(sizeof(int) * 100);
	}
	return 0;
}

在这里插入图片描述
可以看出内存的使用突然升高了,这里可能由于电脑的保护措施所以,不明显,那我们如何解决这个问题呢,这就要靠free函数了

free函数的使用

# include<stdio.h>
# include<stdlib.h>
# include<errno.h>
int main()
{
   
	while (1)
	{
   
		int* p = (int*)malloc(sizeof(int) * 100);
		free(p);
		p = NULL;
	}
	return 0;
}

只要在程序后面加入free就可以解决这个问题了,free的本质就是将内存释放,但是释放之后指针还指向那个内容,所以我们就应该将这个指针置为空指针,这样我们就能愉快的使用动态内存申请了

正文到此结束