当前位置: 首页 > news >正文

C语言动态内存管理、柔性数组(超详细版)

目录

何为动态内存管理?​​​​​​​

动态内存函数介绍

mallo()函数

free()函数

calloc()函数

realloc()函数

realloc()的两种执行原理

内存池

常见的动态内存错误

对NULL解引用

对开辟的空间越界访问

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

使用free释放动态内存的一部分

对同一块内存多次释放

内存泄漏

经典面试题​​​​​​​

柔性数组



 

📌————本章重点————📌

🔗realloc()的两种执行原理

🔗内存池

🔗常见的动态内存错误

🔗经典面试题

🔗柔性数组


 ✨————————————✨


何为动态内存管理?

        亦称:动态内存分配。编写程序有时不能确定数组应该定义为多大,因此这时在程序运行时要根据需要从系统中动态地获得内存空间。

        所谓动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。

        其涉及到几种内存函数:malloc()、calloc()、realloc()、free()等;


动态内存函数介绍

mallo()函数

  • void* malloc (size_t size);
  • malloc向内存申请一块连续可用的空间;
    • 如果开辟成功,则返回一个指向开辟好空间的地址;
    • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查,否则会经常在vs中看到警告:取消对指针NULL的引用;
    • 该函数定义时返回值为void*,因此使用者需强制所需类型;
    • size的值不能为0,这是标准未定义的;

使用实例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

int main()
{
	int* arr = (int*)malloc(10 * sizeof(int));
	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		for (int i = 0; i < 10; i++)
		{
			*(arr + i) = i;
			printf("%d ", arr[i]);
		}
	}

	return 0;
}

//输出1 2 3 4 5 6 7 8 9 10

这里用完后虽然没有free,并不是说空间就不回收了,对于malloc,当程序退出时,系统会自动回收其开辟的空间。

free()函数

  • void free (void* ptr);
  • free函数是专门用来做动态内存的释放和回收的;
  • 注意:
    • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的;
    • 如果参数ptr是NULL指针,则函数什么事都不做;


使用实例:

int main()
{
	//开辟
	int* p = (int*)malloc(40);
	//使用
	// ...
	//释放
	free(p);
	p = NULL;//让p失忆,永远不知道用过的地址

	return 0;
}

calloc()函数

  • void* calloc (size_t num, size_t size);
  • calloc函数也用来动态内存分配;
  • 该函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0,这里便是与malloc的不同之处;

使用实例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));

	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		for (int i = 0; i < 10; i++)
		{
			*(arr + i) = i;
			printf("%d ", arr[i]);
		}
	}
	//释放
	free(arr);
	arr = NULL;

	return 0;
}

realloc()函数

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

  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,realloc函数可以使分配更加灵活:

    • ptr是要调整的内存地址;

    • size为调整之后的总大小;

    • 返回值为调整之后的内存起始位置;

    • r ealloc在调整内存空间的是存在两种情况:
      • 情况1:原有空间之后有足够大的空间;
      • 情况2:原有空间之后没有足够大的空间;

使用实例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));

	if (arr == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		for (int i = 0; i < 10; i++)
		{
			*(arr + i) = i;
			printf("%d ", arr[i]);
		}
	}
	//用完刚才40个空间后还要继续放数据——扩容
	//先用一个新的指针来接收,防止扩容失败返回NULL
	//再追加40个字节,现在总共有80个字节
	int* ptr = (int*)realloc(arr, 20 * sizeof(int));
	if (ptr != NULL)
	{
		arr = ptr;
		for (int i = 0; i < 10; i++)
		{
			*(ptr + i) = i + 10;
			printf("%d ", ptr[i]);
		}
	}
	//释放
	free(arr);
	arr = NULL;

	return 0;
}


//输出:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

realloc()的两种执行原理:

即对于上述realloc调整空间大小的两种情况:

1.原内存之后有足够大的空间:

2.原内存之后没有足够大的空间:

由此我们引发思考:如果频繁的这样申请空间,不仅每次访问操作系统都会降低效率,还有可能导致空间碎片化,于是这一问题又被内存吃很好的解决了;

内存池:

        内存池,则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升,并且一定程度上会避免空间碎片化,但这并不是绝对的,如果频繁使用内存池,也会导致同样的问题出现。


常见的动态内存错误

对NULL解引用:

下面这段代码中p虽然不是NULL,但若在某些特殊情况下开辟失败,就是对NULL进行引用;

对开辟的空间越界访问:

该问题较为常见,无论在静态内存还是动态内存中,我们都应养成防止越界的好习惯;

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

int main()
{
	int a = 20;
	int* pa = &a;

	printf("%d\n", *pa);

	free(pa);
	pa = NULL;

	return 0;
}

使用free释放动态内存的一部分:

要释放就得从开始将整个空间释放掉,否则就无法找到这块空间的起始位置;

int main()
{
	int* p = (int*)malloc(8);

	p += 1;//p此时已不指向起始位置

	free(p);
	p = NULL;

	return 0;
}

对同一块内存多次释放:

内存泄漏:

int main()
{
	int* p = NULL;

	while (1)
	{
		if (p != NULL)
		{
			p = (int*)malloc(40);
			*p = 20;
		}
	}

	free(p);
	p = NULL;

	return 0;
}


经典面试题

题一:

        p作为参数在函数内使用完后就被销毁了,此时的str依然是NULL,因此会发生内存泄露、程序崩溃。

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

怎么写才对呢?

题二:

        返回局部变量或临时地址,此处的p成为野指针。

        p在此处是局部变量、临时地址,它的内容会随着函数的调用而产生,随着函数的关闭而回收,但最终返回了p的地址,此时p所指向的空间已经被释放,因此成为了野指针。

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

题三:

warning C4172: 返回局部变量或临时变量的地址: a; 

int* test()
{
    //返回栈空间的地址的问题
    int a = 10;
    return &a;
}
int main()
{
    int* p = test();
    printf("hehe\n");
    printf("%d\n", *p);

    return 0;
}

为什么执行打印hehe后输出不再是10了呢?

 这是因为hehe覆盖了之前一部分栈区

题四:

        这段代码的错误出现在语句4,有的小伙伴可能会有疑惑:虽然str的空间被释放了,但是我可以拿到它的地址,既然有它的地址就可以把字符串拷贝进去啊?其实形象的比喻就像男女朋友,str变量是男生,str的内容是女生,free(str)就相当于两个人分手,既然分手了,知道人家的地址,也不能在去找她了呀。

        在程序的层面理解即为:str的变量是为Test函数创建的,str的空间也是随着str的产生而开辟的,对str进行free就,虽然str的地址依然存在,但是该程序段已经不存在属于它的空间了。

void Test(void)
{
	char* str = (char*)malloc(100); //1
	strcpy(str, "hello"); //2
	free(str); //3
	if (str != NULL)
	{
		strcpy(str, "world"); //4
		printf(str); //5
	}
}


柔性数组

结构体中的最后一个元素允许是未知大小的数组,就叫柔性数组成员;

特点:

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

示例:下面这个结构的大小就是4个字节,只包含n的大小,不包含a的大小。

#include<stdio.h>

struct S
{
	int n;
	int a[];//数组a的大小未知,则a就是柔性数组成员
};
int main()
{
	printf("%zd\n", sizeof(struct S));

	return 0;
}

使用实例:

#include<stdio.h>
#include<stdlib.h>

struct S
{
	int n;
	int a[];//数组a的大小未知,则a就是柔性数组成员
};
int main()
{
	struct S* s = (struct S*)malloc(sizeof(struct S) + 40);
	if (s == NULL)
	{
		//报错
		return;
	}
	s->n = 10;
	printf("%d\n", s->n);
	for (int i = 0; i < 10; i++)
	{
		s->a[i] = i + 1;
		printf("%d ", s->a[i]);
	}

	free(s);
	s = NULL;

	return 0;
}


//输出:
//10
//1 2 3 4 5 6 7 8 9 10

当扩容时,就能真正体现其柔性的特点了:

#include<stdio.h>
#include<stdlib.h>

struct S
{
	int n;
	int a[];//数组a的大小未知,则a就是柔性数组成员
};
int main()
{
	struct S* s = (struct S*)malloc(sizeof(struct S) + 40);
	if (s == NULL)
	{
		//报错
		return;
	}
	s->n = 10;
	printf("%d\n", s->n);
	for (int i = 0; i < 10; i++)
	{
		s->a[i] = i + 1;
		printf("%d ", s->a[i]);
	}
	//给数组a扩容到80个字节
	struct S* str = (struct S*)realloc(s, sizeof(struct S) + 80);
	if (str == NULL)
	{
		//报错
		return;
	}
	s = str;
	str = NULL;
	//这里的str不能释放掉,str和p指向用一块空间,如果释放掉,p就成了野指针
	for (int i = 10; i < 20; i++)
	{
		s->a[i] = i + 1;
		printf("%d ", s->a[i]);
	}
	free(s);
	s = NULL;

	return 0;
}


//输出:
//10
//1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

优点:

1.方便内存释放:

        假设我们的程序交给用户使用,而用户并不知道整个结构中内存开辟了多少次,用户只会使用free,释放掉整个结构,如果结构内定义的是局部变量指针一类,那么用户并不会知道该结构内部的成员也要释放,因此当我们使用柔性数组作为结构体成员,为用户一次性开辟好空间,用户的每次释放就可以将整个空间释放掉了。

2.有利于提高访问速度,避免连续开辟引起的空间碎片化。

相关文章:

  • 【USB设备设计】-- CDC 设备开发(虚拟串口设备)
  • 用ARM进行汇编语言编程(3)逻辑移位和轮换,条件与分支
  • maltab datenum函数与正则表达式巧用:逐日数据转为逐月数据、日序转月序
  • PTA JAVA02 基础语法1
  • C++ 语言学习 day06 string , 异常
  • Linux命令`ll`的结果解析
  • 查题校园公众号查题系统
  • git分布式版本控制系统
  • C++ Color the ball
  • mysql的基础操作语句
  • Cookie/Session
  • java抽象类和接口(Comparator和Conparable的使用)
  • 百度首个江苏智算中心落地 携手盐城共建200P算力规模
  • 并发编程(四)---设计模式
  • GitHub:建立仓库,本地上传与更新内容
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Golang-长连接-状态推送
  • Hexo+码云+git快速搭建免费的静态Blog
  • Java IO学习笔记一
  • Java编程基础24——递归练习
  • Promise初体验
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Vue UI框架库开发介绍
  • 创建一个Struts2项目maven 方式
  • 订阅Forge Viewer所有的事件
  • 我看到的前端
  • 我是如何设计 Upload 上传组件的
  • 学习ES6 变量的解构赋值
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • # 安徽锐锋科技IDMS系统简介
  • #AngularJS#$sce.trustAsResourceUrl
  • #pragma 指令
  • (1)bark-ml
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (黑马C++)L06 重载与继承
  • (十八)三元表达式和列表解析
  • (译)2019年前端性能优化清单 — 下篇
  • (转)编辑寄语:因为爱心,所以美丽
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .cfg\.dat\.mak(持续补充)
  • .gitignore文件_Git:.gitignore
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET 8.0 中有哪些新的变化?
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .Net 高效开发之不可错过的实用工具
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET的微型Web框架 Nancy
  • .NET多线程执行函数
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • ?php echo ?,?php echo Hello world!;?
  • []我的函数库