IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言入门学习——动态内存管理 -> 正文阅读

[C++知识库]C语言入门学习——动态内存管理


平常常用的开辟内存方式有:

int i = 20;	在栈区开辟一个4字节大小的空间
char ch[10] = {0};	在栈区开辟一个连续的10字节大小空间

但是,在栈区开辟的内存是固定大小的,数组在申明的时候必须申明长度;而有时候当我在程序运行以后才知道具体需要多大的内存空间,比如实际只需要3个数字长度(3字节),那就造成了内存浪费,这时就可以用动态内存来分配

malloc

#include <stdlib.h>
void* malloc (size_t size); 

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
1.如果开辟成功,则返回一个指向开辟好空间的指针。
2.如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

请看代码:

int main()
{
	申请空间
	int* p = (int*)malloc(sizeof(int)*10);
	在堆区,申请一块连续的40字节大小的内存空间,并把首地址返回
	由于不知道使用者是什么数据类型,所以是void*,需要强制转为需要使用的数据类型
	
	if(p == NULL)
	有可能申请失败,那就会返回null,所以最好在申请完判断一下	
	{
		perror("malloc");	如果为空就打印错误信息
		return 1;
	}
	
	使用这块空间
	int i;
	for(i = 0;i < 10;i++)
		printf("%d\n",(p[i]=i));
	return 0;
}

在这里插入图片描述

free

当程序结束时,系统会自动回收内存,但是当程序没结束时,总是申请内存,动态内存包括大部分的虚拟内存,而由于虚拟内存的操作是要读写磁盘,因此极大地影响系统的性能。你的系统很可能因此而崩溃那很容易就被占满,就会影响系统系能导致崩溃。
所以申请后使用完要及时的释放,是一种良好的使用习惯

void free (void* ptr);

注意:
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
3.释放必须是申请内存的首地址传参

int main()
{
	申请空间
	int* p = (int*)malloc(sizeof(int)*10);
	在堆区,申请一块连续的40字节大小的内存空间,并把首地址返回
	由于不知道使用者是什么数据类型,所以是void*,需要强制转为需要使用的数据类型
	
	if(p == NULL)
	有可能申请失败,那就会返回null,所以最好在申请完判断一下	
	{
		perror("malloc");	如果为空就打印错误信息
		return 1;
	}
	
	使用这块空间
	int i;
	for(i = 0;i < 10;i++)
		printf("%d\n",(p[i]=i));

	free(p);	p是申请内存空间的首地址
	释放完以后,申请的内存空间就会被收回,里面的内容就会被系统重置了
	
	这时指针就会为一个野指针,只保留了地址,但不知道里面是什么内容
	所以在释放完以后,需要把指针也置NULL
	p = NULL;	
	return 0;
}

calloc

void* calloc(size_t num,size_t size);

1.函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

请看代码:

int main()
{
	int* p = (int*)calloc(10,sizeof(int));
	申请 10块连续的,大小为4字节的空间,并把首地址返回给p
	if(NULL == p)
	{
		perror("calloc");
		return 1;
	}
	int i;
	for(i = 0;i<10;i++)
		printf("%d ",*(p+i));
	
	free(p);
	p = NULL;
	
	return 0;
}

在这里插入图片描述
在这里插入图片描述
如果要求对申请的空间初始化,那么就可以用calloc

realloc

有时等我们发现开辟的内存不够或者多了,就可以使用realloc
realloc函数的出现让动态内存管理更加灵活,可以做到对动态开辟内存大小的调整

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

1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.当向后追加的内存被占用或不够了,就会重新找一块最够的内存,并把原先的内容复制到新的内存上,并把原先的内存free掉
4.返回值为调整之后的内存起始位置(原内存后续不够时)。
5.扩容失败时返回NULL

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

	int i;
	for(i = 0;i < 10;i++)
		printf("%d ",(p[i] = 1));

	这时想保留20个内容,则使用 realloc
	int* fp = (int*)realloc(p,20*4);
	由于realloc也有扩容失败的风险,所以用新一个指针接收地址
	若返回的不是空,则在把接收的地址赋给原来的指针
	if(fp != NULL)
	{
		p = fp;
	}
	扩容成功

	for(i=0;i<20;i++)
		*(p+i) = i;

	free(p);
	p = NULL;
	除了释放空间,把原指针置空以外,还要把接收扩容返回地址的指针也要置空
	不然等于是,只存了一个地址,不知道地址里是什么的野指针
	fp = NULL;
	
	return 0;
}

在这里插入图片描述

常见错误

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

void test()
{
 	int *p = (int *)malloc(INT_MAX/4);
 	*p = 20;//如果p的值是NULL,就会有问题
 	free(p);
}
正确做法是:

void test()
{
 	int *p = (int *)malloc(INT_MAX/4);
 	申请完后,进行对空的判断
 	if(p == NULL)
 	{
		perror("malloc");
		return 1;
	}
 	*p = 20;
 	free(p);
}

在申请动态内存后,一定要对指针接收的返回值进行判断

2.越界访问

void test()
{
	 int i = 0;
	 int *p = (int *)malloc(10*sizeof(int));
	 if(NULL == p)
	 {
		 exit(EXIT_FAILURE);		//1
	 }
	 for(i=0; i<=10; i++)
	 {
		 *(p+i) = i;				当i是10的时候越界访问
		
	 }

	 或者 这里用 *p++ = i时
	  for(i=0; i<10; i++)
	 {
		 *p++ = i		当i是9的时候,p是先输出后自增,此时p就等于 p+10}					而且free(p)也是有问题的,这里已经不是首地址了
	 free(p);
}

在平时编程中一定要对循环边界仔细检查

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

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//			error
}
a是在栈区开辟内存的,由编译器自动释放,调用结束就自动销毁了

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

void test()
{
	 int i = 0;
	 int *p = (int *)malloc(10*sizeof(int));
	 if(NULL == p)
	 {
		 exit(EXIT_FAILURE);		//1
	 }
	 for(i=0; i<10; i++)
	 {
		 *p++ = i		当i是9的时候,p是先输出后自增,此时p就等于 p+10}					而且free(p)也是有问题的,这里已经不是首地址了
	 free(p);
}

必须记住起始地址
最好不要对指针本身存储的地址进行改变,应该让指针指向内存去访问,而不是让指针改变存的内存去访问

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

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);   error  重复释放
}
//free(0)表示未定义,有的编译器会报错

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

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

int main()
{
	 test(); 
	 test在调用结束后,里面的变量等都会被销毁(栈区),而在test函数体中申请的动态内存(堆区)不会
	 而调用结束后,没有返回申请的首地址,也没有释放,接收地址的指针变量随着调用结束销毁
	 等于是通往这个内存的入口丢失了,会导致无法再访问这个内存了,则造成内存泄漏
	 while(1);
}

在哪开辟的内存,就在哪释放,若后续还要使用,那就返回内存首地址

经典例题:
例1.

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

例1中,str是空指针,传参后,形参是实参的一份临时拷贝,等于p也是NULL;GetMemory函数体中申请完了动态内存,虽然让p接收保存首地址,但是他没有返回该地址,也没有释放该地址,GetMemory调用结束,p也随即销毁,“通往申请的动态内存入口丢失” ,造成内存泄漏,str没变还是空指针,所以也不会打印hello world

例2.

char *GetMemory(void) 
{
	 char p[] = "hello world";
	 //char* p就可以了
	 return p; 
}
void Test(void) 
{
	 char *str = NULL;
	 str = GetMemory();		//野指针
	 printf(str);
}

在GetMemory(void) 创建了一个数组并保存了一个字符串,虽然返回了指针p保留的 “字符串首地址” ,但由于GetMemory函数在栈区,调用结束后,内存被系统收回并重置成别的内容。hello world所在的地址也属于栈区,是临时创建的,调用结束后也会随着被销毁并重置;此时指针p等同于,保留了一个地址,但是地址里现在是什么内容不知道,成为了野指针

例3.

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

这里是先释放,后使用。释放完后,内存已经还给系统了,虽然str存的是返回的首地址,但此时该地址存的是什么就不知道了,变成了野指针

柔性数组

结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

struct stu1
{
	int num;
	double sore;
	int arr[];int arr[0]
};

int main()
{
	printf("%d",sizeof(struct stu1));  //  16
	struct stu1* ps = (struct stu1*)malloc( sizeof(struct stu1) + 40 );
	
	sizeof(struct stu1)int num和 double sore的空间大小共16,后面的40是给数组的空间,等于是int arr[10]
	柔性体现在:动态开辟的内存后面多加的40内存空间是给数组用的,若不够用,还可以进行修改(可大可小)
	这样就可以根据需求随时调整数组大小,而且这样开辟也是连续的,也不影响释放
}

如上述代码中,结构体中,最后一个成员是数组,且未知大小,就是柔性数组成员

柔性数组的特点:
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

总结

1.malloc、calloc在申请完后 最好对返回值进行判断
2.realloc在扩容后,要新定义一个指针接收,并判断不为空后再赋给原来的指针
3.申请了动态内存要记得释放,给free的实参必须是申请内存空间的首地址
4.不可对非动态内存进行释放
5.不可对一个动态内存多次释放
6.free(0)属于未定义
7.要注意越界问题,在编写完后对边界要仔细检查
8.要避免出现野指针,不用的指针及时置空
9.要避免内存泄漏
10.在不同的函数中申请动态内存,要做到谁申请谁释放,或者返回该地址在不需要使用后及时释放
11.柔性数组的作用在于,动态申请内存时可大可小
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:54:41  更:2022-09-13 10:56:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 15:35:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码