C语言——动态内存管理

目录

既然选择远方,当不负青春,砥砺前行

一、 Why要有动态内存的开辟?

在此之前我们已经学会了静态的内存开辟。

c 复制代码
#include<stdio.h>
int main()
{

	int a = 10;
	int arr[10] = { 1,2,3,4,5,6 };

	return 0;
}
  • 但是静态开辟的内存空间大小是固定的。
  • 对于数组,一旦长度确定后,就不能在改变了。

但是在实际的开发中,我们需要的空间事先一开始并不知道,而是在程序运行的时候才知道。 所以引入了动态内存的开辟,让我们自己申请和释放空间。

那么在自己申请和释放空间时就一定会用到一下四个函数,掌握了以下四个函数的使用,也就会自己进行动态内存的管理了!!!

简单讨论一下分区

开辟动态内存是在堆区开辟,使用下面四个函数要包含头文件 #include<stdlib.h>

  • 栈区:先进后出,程序一开始先从main()进入,main()入栈,为局部变量,函数调用,形式参数,返回值在栈上分配空间,再调用各种函数入栈,出栈。最后mian()出栈,程序执行完毕。栈是有大小的,无限次递归会造成栈溢出。

  • 堆区:自己动态地向内存分配空间的区域。malloc calloc realloc 动态开辟内存函数,free 释放动态内存开辟的函数。如果忘记释放动态分配的内存空间可能会导致内存泄露。

  • 静态区:存放静态变量和全局变量的区域,这些变量的作用域为整个main()函数,生命周期和程序的生命周期一样,只有程序结束,这些变量才会被回收。

二、malloc

C语⾔动态内存开辟的函数之一:

c 复制代码
//声明:
 void* malloc (size_t size)
 
  • 返回值类型是void * 自己想开辟啥类型的空间,可以自己强转。
  • 参数 size 为所要开辟空间的字节数,如果传入的size=0,一般编译器啥也不会报错。
  • 如果开辟成功则返回一个指向开辟好空间的指针
  • 如果开辟失败则返回一个NULL指针,所以再开辟空间后一定要检查指针是否为NULL

使用:

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>


int main()
{
	
	int* p = (int* )malloc(4*5); //动态开辟5个整形的空间
	if (p == NULL) //检查指针
	{
		perror("malloc"); //打印错误信息
	}
	//使用
	for (int i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}

	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}

	return 0;
}
  • 内存
  • 输出

三、free

C语言专门用来做------动态内存的释放和回收。

c 复制代码
void free (void* ptr)
  • 参数ptr指向的空间一定要是动态开辟的,否则报错。ptr 一定指向空间的起始地址
  • 如果ptr是NULL,则啥也不干。
c 复制代码
#include<stdio.h>
#include<stdlib.h>


int main()
{
	
	int* p = (int* )malloc(4*5); //动态开辟5个整形的空间
	if (p == NULL)
	{
		perror("malloc");
	}
	//使用
	for (int i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}

	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}

	
	free(p);//释放p所指向的动态内存
	p = NULL; 
	//既然释放了,p指向的内存空间也已经被回收了。
	//所以p置为NULL更安全

	return 0;
}
  • 释放的空间不是动态开辟的,报错

四、calloc

calloc 函数也⽤来动态内存分配,那和malloc 有什么不同呢?

c 复制代码
 void* calloc (size_t num, size_t size) 
  • 参数 num 为开辟空间类型的个数
  • 参数 size 未开辟空间类型所占字节的大小
  • 与函数 malloc 的区别只在于 calloc 会把申请的空间的每个字节初始化为0
c 复制代码
#include<stdio.h>
#include<stdlib.h>


int main()
{
   
   int* p = (int* )calloc(5,sizeof(int)); //动态开辟5个整形的空间
   if (p == NULL)
   {
   	perror("calloc");
   	return -1;
   }
   //输出
   for (int i = 0; i < 5; i++)
   {
   	printf("%d ", p[i]);
   }

   
   free(p);
   p = NULL; 


   return 0;
}

如果我们所要开辟的空间要进行初始化,可以使用calloc。

五、realloc

realloc函数的出现则让动态内存管理更加灵活!!!

虽然我们可以动态地开辟内存,但是难免会出现,申请的空间过大或者过小的情况。于是realloc 可以再次对动态开辟的空间进行调整。

c 复制代码
void* realloc (void* ptr, size_t size)
  • 参数ptr 指向要调整的内存,ptr 一定指向空间的起始地址
  • 参数size 为调整后,新的空间所占字节数
  • 返回新开辟的空间地址

这个函数是怎么调整的呢?
在原有内存的基础上,将其拷贝一份到新调整的空间,释放原来的空间,返回新的空间起始地址!

有如下三种情形:

1.原有空间后面有足够大的空间,容得下调整之后的空间大小。

2.原有空间后面不足够大的空间,容不下调整之后的空间大小。

3.调整空间失败。

  • 1.原有空间后面有足够大的空间
c 复制代码
- #define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>


int main()
{
   
   int* p = (int* )calloc(5,sizeof(int)); //动态开辟5个整形的空间
   if (p == NULL)
   {
   	perror("calloc");
   }
   //调整p指向的空间,从原来5个int --> 10个int
   int* tmp = (int*)realloc(p, sizeof(int) * 10);
   if (tmp == NULL) 
   {
   	perror("realloc");
   	return -1;
   }

   p = tmp;

   //使用
   for (int i = 0; i < 10; i++)
   {
   	p[i] = i + 1;
   }
   //打印
   for (int i = 0; i < 10; i++)
   {
   	printf("%d ", p[i]);
   }

   free(p);
   p = NULL; 

   return 0;
}
  • 2 原有空间后面不足够大的空间
c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>


int main()
{
	
	int* p = (int* )calloc(5,sizeof(int)); //动态开辟5个整形的空间
	if (p == NULL)
	{
		perror("calloc");
	}
	//调整p指向的空间,从原来5个int --> 20个int

	int* tmp = (int*)realloc(p, sizeof(int) * 20);
	if (tmp == NULL) 
	{
		perror("realloc");
		return -1;
	}

	p = tmp;

	//使用
	for (int i = 0; i < 20; i++)
	{
		p[i] = i + 1;
	}
	//打印
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL; 

	return 0;
}
  • 3 调整的空间太大了,调整空间失败。
c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>


int main()
{
	
	int* p = (int* )calloc(5,sizeof(int)); //动态开辟5个整形的空间
	if (p == NULL)
	{
		perror("calloc");
	}
	//调整p指向的空间,从原来5个int --> #define LLONG_MAX  9223372036854775807i64

	int* tmp = (int*)realloc(p, sizeof(int) * LLONG_MAX);
	if (tmp == NULL) 
	{
		perror("realloc");
		return -1;
	}

	p = tmp;

	//使用
	for (int i = 0; i < 20; i++)
	{
		p[i] = i + 1;
	}
	//打印
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL; 

	return 0;
}

六、常见的6大动态内存错误

1.对NULL指针的解引用操作

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{

	int* p = (int*)malloc(INT_MAX);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

int main()
{
	test();

	return 0;
}

2.对非动态内存的空间进行释放

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{
	int a = 6;
	int* p = &a;//对非动态内存的空间进行释放
	free(p);
}

int main()
{
	test();

	return 0;
}

3.对动态开辟的空间进行越界访问

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{
	
	int* p = (int*)calloc(10,sizeof(int));
	for (int i = 0; i <= 10; i++)
	{
		printf("%d ", p[i]); //对动态开辟的空间进行越界访问
	}

	free(p);
}

int main()
{
	test();

	return 0;
}

4.动态开辟空间后,未释放,造成空间泄露

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{
	
	int* p = (int*)calloc(10,sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//动态开辟空间后,未释放,造成空间泄露
	
}

int main()
{
	test();

	return 0;
}

5.对一块动态开辟的空间进行多次释放

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{
	
	int* p = (int*)calloc(10,sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	free(p);//对一块动态开辟的空间进行多次释放
}

int main()
{
	test();

	return 0;
}

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

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void test()
{
	
	int* p = (int*)calloc(10,sizeof(int));
	p++; //使用free释放一块动态开辟内存的一部分
	free(p);
}

int main()
{
	test();

	return 0;
}

七、经典笔试4道题分析

1.笔试题一

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}

void test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	test();

	return 0;
}

2.笔试题二

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

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

int main()
{
	test();

	return 0;
}

3.笔试题三

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

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
}

int main()
{
	test();

	return 0;
}

4.笔试题四

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

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


int main()
{
	test();

	return 0;
}
相关推荐
好奇的菜鸟几秒前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
所待.3834 分钟前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood8 分钟前
Java线程池详解
java·线程池·多线程·性能
Alive~o.09 分钟前
Go语言进阶&依赖管理
开发语言·后端·golang
Chef_Chen11 分钟前
从0开始学习机器学习--Day33--机器学习阶段总结
人工智能·学习·机器学习
搏博12 分钟前
神经网络问题之:梯度不稳定
人工智能·深度学习·神经网络
花海少爷12 分钟前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-12 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
GL_Rain29 分钟前
【OpenCV】Could NOT find TIFF (missing: TIFF_LIBRARY TIFF_INCLUDE_DIR)
人工智能·opencv·计算机视觉
喵叔哟32 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构