【c语言进阶】动态内存管理知识大全(下)

news2024/9/24 1:20:50

在这里插入图片描述

🚀write in front🚀
📜所属专栏c语言学习
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!

在这里插入图片描述

文章目录

  • 前言:
  • 一:经典练习题
    • 1.1:题一:
    • 1.2:题二:
    • 1.3:题三:
    • 1.4:题四:
  • 二:C/C++程序的内存区域的划分
  • 三:柔性数组:
    • 1.柔性数组的特点:
    • 2.柔性数组的使用:
    • 3.柔性数组的优势:
  • 总结:

前言:

  在上一篇文章中,我们讲解了动态内存管理的部分知识:动态内存管理知识大全(上)今天,我们先通过几道题目复习一下,并且学习柔性数组的相关知识!

一:经典练习题

1.1:题一:

下面代码的运行结果是?

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;
}

这段代码运行错误,原因如下:

  • GetMemory函数是传值调用,str传给p的时候,p是str的临时拷贝有自己独立的空间,当Germemory函数内部申请了空间后,地址放在p中时,str依然是NULL。当GetMemory函数返回之后,strcpy拷贝的时候,形成了非法访问内存
  • 在GetMemory函数内部,动态申请了内存,但是没有释放,会出现内存泄漏
    在这里插入图片描述
    正确写法:
//方法一:
void GetMemory(char** p)//形参用二级指针接收,此时p里面存的是str的地址
{
	*p = (char*)malloc(100);//*p得到str,让str指向新开辟的空间
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);//址传递
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

//方法二:
char*  GetMemory(char* p)
{
	p=(char*)malloc(100);
	return p;
}
void Test(void)
{
	char* str = NULL;
	str=GetMemory(str);//址传递
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

1.2:题二:

下面代码的运行结果是?

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

int main()
{
	Test();
	return 0;
}

上面代码打印出来的是:烫烫烫烫烫烫烫烫圉7。原因如下:

  • 数组p是一个局部变量,在栈区,根据函数栈帧的知识我们知道,在出 GetMemory 函数的时候,数组 p 的内存空间就被销毁了,还给了操作系统,虽然把这个数组首元素的地址返了回去,但此时再通过地址去访问这一块空间,就成了非法访问。这种问题通常也被叫做返回栈空间地址的问题.

正确代码:

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

int main()
{
	Test();
	return 0;
}

1.3:题三:

下面代码的运行结果是?

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

int main()
{
	Test();
	return 0;
}

  这段代码可以成功打印出hello,但是仔细观察就能发现,这段代码里面之见 malloc 却不见 free 这就是典型的内存泄漏

知识补充:
  肯定有很多家人们会问为什么这里可以直接printf(str),其实啊printf在打印字符串的时候,无论是括号里放进去是“…”的的字符串常量,还是一个字符指针,本质上都是传进去了首元素地址,所以我们printf(“hello”)和直接printf(str)是一样的,因为str装着hello的首元素h的地址。至于为什么以前一定要用%s打印是为了在多种类型变量一起打印时好分别开来。

正确代码:

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;
}

int main()
{
	Test();
	return 0;
}

1.4:题四:

下面代码的运行结果是?

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;
}

这段代码可以成功打印出world。但上面这段代码是有问题的,因为我们已经把 str 给 free 掉了,意思也就是,已经把这块空间归还给操作系统了,这块空间的操作权限属于操作系统。在 free 完后没有把 str 置为空,所以 str 还是指向那块空间,此时的 str 已经变成了一个野指针,后面一些列涉及 str 的操作都属于非法访问。正确的做法是在 free 的后面,把指针置为空。

二:C/C++程序的内存区域的划分

  通过以上的题目我们发现,错误基本都是内存泄漏,非法访问,出函数该栈区被释放出现的问题。所以大家一定要弄清楚哪些在堆区,那些在栈区,这些变量会在什么时候销毁等等问题。

  • 栈区(Stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  • 堆区(heap):由程序员分配释放管理,一般由 malloc,new等内部存储函数使用, 如果没收回,程序结束时由操作系统收回。创建堆时,一般在堆的头部 用一个字节存放堆的大小;回收堆时,通过查看这个 字节的内容,可得知需要释放的多大的内存。
  • 全局区或静态区:存放 全局变量静态变量程序结束时由系统释放,分为全局初始化区全局未初始化区
  • 常量区:存放 常量结束时由系统释放
  • 程序代码区上面4个区统称数据区:存放运行 或准备运行的程序代码,由系统调度

三:柔性数组:

  在 C99 标准中,结构中的最后一个元素的大小允许是未知大小的数组,而我们就将这个未知大小的数组称为柔性数组,并将这个数组成员称为柔性数组成员

typedef struct A
{
	int a;
	int arr1[0];
	//柔性数组成员
}A;
 
typedef struct B
{
	int b;
	int arr2[];
	//柔性数组成员
}B;

1.柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少存在一个其他成员
  • 柔性数组只能作为结构的最后一个元素,并且柔性数组的大小是不确定的。
  • sizeof 返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

例如:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

  柔性数组只能作为结构的最后一个元素,并且柔性数组的大小是不确定的。其实家人们在看sizeof()结果的时候就可以知道为什么柔性数组前面必须有其他成员了,因为sizeof返回的结构体大小不包括柔性数组的内存,所以如果没有其他成员的话,该结构体所占空间大小为0.这是不可能的。

2.柔性数组的使用:

  既然柔性数组的大小是不确定的,我们如何使用他呢?有了之前的动态内存管理函数的学习,再来研究柔性数组的使用就十分简单了,我们直接来看示例:

typedef struct test
{
	int i;
	int arr[];
}test;
 
int main()
{
	test a;
	//定义test类型结构体a
 
	test* p = (test*)malloc(sizeof(test) + 40);
	//malloc函数的返回值为指针类型,故使用结构体指针test*
	//使用malloc函数动态分配空间:test类型结构体的大小(不包含柔性数组) + 40字节
	if (p == NULL)
	{
		perror("malloc");
		//判断动态内存空间是否开辟成功
		return 1;
	}
 
	//业务处理1:
	p->i = 10;
	int i = 0;
	//给柔性数组元素赋值:
	for (i = 0; i < p->i; i++)
	{
		p->arr[i] = i;
	}
	//打印柔性数组元素:
	for(i=0;i<p->i;i++)
	{
		printf("%d ", p->arr[i]);
	}
	printf("\n");
 
	test* pp = (test*)realloc(p, sizeof(test) + 80);
	//使用realloc函数将结构指针指向的结构a进行扩容
	if (pp == NULL)
	{
		perror("realloc");
		//判断动态内存空间是否扩容成功
		return 1;
	}
 
	//业务处理2:
	pp->i = 20;
	for (i = 0; i < pp->i; i++)
	{
		pp->arr[i] = i + 9;
	}
	for (i = 0; i < pp->i; i++)
	{
		printf("%d ", pp->arr[i]);
	}
	printf("\n");
 
	free(pp);
	pp = NULL;
 
	return 0;
}

在这个示例中,我们首先定义了 test 类型结构体 a,接着使用了 malloc 函数为结构体中的 i与柔性数组分配了动态存储空间;接着在判断非空(动态内存空间分配成功)后给结构体成员 i 与柔性数组 arr 内元素赋值,并进行了打印;再接下来,我们通过使用 realloc 函数包含柔性数组 arr 的结构体 a 扩容,并在扩容后给结构体内各成员重新赋值并打印;最后释放使用完毕的动态内存空间并将指针置空:
在这里插入图片描述

3.柔性数组的优势:

  但是同时我们又发现,我们使用柔性数组的目的在于希望使结构成员的空间变为动态,可大可小,那么为什么我们不将其写成指针由指针成员来指向其它的空间呢?如下:

typedef struct test
{
	int i;
	int* p;
}test;
 
int main()
{
	test* ptr = (test*)malloc(sizeof(test));
	if (ptr == NULL)
	{
		perror("malloc");
		return 1;
	}
	ptr->p = (int*)malloc(40);
	if (ptr->p == NULL)
	{
		free(ptr->p);
		p=NULL;
		free(ptr);
		ptr = NULL;
		return 1;
	}
 
	return 0;
}

  在这里我们发现,虽然两段代码效果一样,但是大家会发现,柔性数组的只用开辟一次空间,而第二个需要开辟两次!并且释放也是一样。
优点:

  • 方便内存释放
  • 有利于提升访问速度
  • 有利于减少内存碎片

总结:

  加上今天内容的学习,我们就可以将动态内存空间灵活的运用在我们的程序代码之中了。并且通过使用并管理动态内存空间,还可以提高我们代码及程序的灵活性和执行效率,有助于我们写出更加优秀的程序。
  更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
c语言学习
算法
智力题
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AxMath使用教程+常用符号与公式(持续更新中)

前言 这两天学了学Latex&#xff0c;主要是为了以后写毕业论文做铺垫&#xff0c;而且Latex在数学公式这一方面&#xff0c;要比Word方便许多&#xff0c;于是我就下载了一款国产的公式编辑器——AxMath。永久会员不贵&#xff0c;只要36元&#xff0c;而且软件很好用&#xf…

vue全家桶(一)基础知识

vue全家桶&#xff08;一&#xff09;1.vue基本使用2.vue模板语法1.差值表达式2.指令1.什么是指令2.数据绑定指令3.双向数据绑定4.事件绑定4.1click4.2事件修饰符4.3按键修饰符4.4属性绑定4.5-指令v-model的本质4.6 样式绑定4.6.1 class样式处理4.6.2 style样式处理4.7 分支循环…

Pytorch深度强化学习案例:基于DQN实现Flappy Bird游戏与分析

目录1 案例介绍2 构造深度Q网络3 经验回放与目标网络4 训练流程5 实验分析1 案例介绍 Flappy Bird是一款由来自越南的独立游戏开发者Dong Nguyen所开发的作品&#xff0c;于2013年5月24日上线。 在Flappy Bird中&#xff0c;玩家只需要用一根手指来操控&#xff1a;点击一次屏…

P49 BFC 块级格式化上下文 块级格式化上下文 BFC渲染区域: 创建BFC的元素,它的自动高度需要计算浮动元素. 高度塌陷例子

目录块级格式化上下文BFC渲染区域:创建BFC的元素&#xff0c;它的自动高度需要计算浮动元素.高度塌陷例子&#xff1a;第一种方法 clearfix::after第二种解决办法 :绝对定位第三种解决办法&#xff1a;overflow: scroll;第四种方法&#xff1a;clearfix hidden创建BFC的元素&am…

21版本FL Studio水果音乐制作软件下载

因为对音乐有一些了解&#xff0c;所以周边有不少朋友会问我很多关于音乐的问题&#xff0c;其中比较多是学习音乐到底用哪款软件比较好。每次遇到这样的问题&#xff0c;我都会告诉他们&#xff0c;就是我一直在用的音乐制作软件FL Studio。音乐制作软件FL Studio&#xff0c;…

【JavaGuide面试总结】Java集合篇·中

【JavaGuide面试总结】Java集合篇中1.Collection 子接口之 SetComparable 和 Comparator 的区别比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同2.Collection 子接口之 QueueQueue 与 Deque 的区别ArrayDeque 与 LinkedList 的区别说一说 PriorityQueue3.Map 接口HashMap 的…

机器学习(八):深度学习简介

文章目录 深度学习简介 一、神经网络简介 二、深度学习各层负责内容 深度学习简介 一、神经网络简介 深度学习&#xff08;Deep Learning&#xff09;&#xff08;也称为深度结构学习【Deep Structured Learning】、层次学习【Hierarchical Learning】或者是深度机器学习【…

React中commit阶段发生了什么

对于commit阶段的主要工作是循环effectList链表去将有更新的fiber节点应用到页面上是commit的主要工作。 EffectList 什么是副作用&#xff1f; 函数在执行过程中对外部造成的影响可以称之为副作用&#xff0c;副作用包含的类型很多&#xff0c;比如说标记值为Placement时&a…

客快物流大数据项目(一百零九):Spring Boot概述

文章目录 Spring Boot概述 一、什么是SpringBoot 二、​​​​​​​为什么要学习Spring Boot

PHP转Go实践:xjson解析神器「开源工具集」

前言 近期会更新一系列开源项目的文章&#xff0c;新的一年会和大家做更多的开源项目&#xff0c;也欢迎大家加入进来。 xutil 今天分享的文章源自于开源项目jinzaigo/xutil的封装。 在封装过程中&#xff0c;劲仔将实现原理以及相关实践思考&#xff0c;写成文章分享出来&am…

Python3学习——条件控制、循环语句与迭代器

目录 一、编程第一步——斐波那契数列 二、条件控制 (一)if/else语句 判断狗狗的年龄&#xff1a; (二)多层if/else嵌套 判断数字能否被2或3整除&#xff1a; (三)match...case匹配——python3中新增 根据数字判断星期&#xff1a; 三、循环语句 (一)while循环 1.循环…

Java:Idea创建项目和Spring工程基本使用

一、创建项目 1、创建新的空的项目&#xff1a; Empty Project–next 2、定义项目的名称&#xff0c;并指定位置 3、对项目进行设置&#xff0c;JDK版本、编译版本 4、添加模块信息 5、修改maven路径 6、项目目录结构 二、搭建Spring的框架 1、在核心配置文件中添加Spring的j…

C++11 并发指南五(stdcondition_variable 详解)

C11 并发指南五(std::condition_variable 详解) 文章目录C11 并发指南五(std::condition_variable 详解)std::condition_variable 类介绍std::condition_variable_any 介绍std::cv_status 枚举类型介绍std::notify_all_at_thread_exit前面三讲《 C11 并发指南二(std::thread 详…

二叉树简单解析(1)

&#x1f340;本人简介&#xff1a; 吉师大一最爱逃课的混子、 华为云享专家、阿里云专家博主、腾讯云自媒体分享计划博主、 华为MindSpore优秀开发者、迷雾安全团队核心成员&#xff0c;CSDN2022年运维与安全领域第15名 &#x1f341;本人制作小程序以及资源分享地址&#x…

英语学习打卡day7

2023.1.27 1.ironically adv.具有讽刺意味的是;反讽地&#xff0c;讽刺地 Ironically, his cold got better on the last day of his holiday. 2.bequeath vt.遗赠;把…遗赠给;把… .传给 (比give更正式) bequeath sb sth bequeath sth to sb Don’t bequeath the problem …

JDK17 || JDK 8 完美 卸载 教程 (Windows版)

文章目录一、卸载jdk程序1 . 找到控制面板2. 卸载程序3. 找到JDK 相关的程序4. 右键 选择卸载程序5. 下一步 选择 是6.下一步 选择 是二、安装 新版 JDK三、如果不想再使用jdk环境结语一、卸载jdk程序 1 . 找到控制面板 2. 卸载程序 3. 找到JDK 相关的程序 4. 右键 选择卸载程…

IDEA界面和控制台的滚动条颜色不明显?赶快换一个吧!

前言 不知道大家是否和我一样有这么一个烦恼&#xff1a; IDEA自带的滚动条颜色很暗&#xff0c;配上一些主题颜色搭配很难发现。 所以今天就想着怎么可以修改滚动条颜色&#xff0c;首先去网上搜了搜都是什么鼠标滚轮加shift滚动&#xff0c;一点也不实用 偶然看到了个不错的…

【青训营】Go的BenchMark的使用

本文内容总结于 字节跳动青年训练营 第五届后端组 Go自带了一些性能测试工具&#xff0c;其中BenchMark是较为重要的一个。 我们以计算斐波那契数列的示例来展示BenchMark的使用 package Benchmarkimport "testing"func Fib(n int) int {if n < 2 {return n}ret…

OpenCV-PyQT项目实战(1)安装与环境配置

本系列从零开始实战解说基于 PyQt5 的 OpenCV 项目开发。 欢迎关注『OpenCV-PyQT项目实战 Youcans』系列&#xff0c;持续更新中 OpenCV-PyQT项目实战&#xff08;1&#xff09;安装与环境配置 OpenCV-PyQT项目实战&#xff08;2&#xff09;OpenCV导入图像 文章目录1. PyQt5 …

初识图像分类——K近邻法(cs231n assignment)

作者&#xff1a;非妃是公主 专栏&#xff1a;《计算机视觉》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 专栏系列文章 Cannot find reference ‘imread‘ in ‘init.py‘ error: (-209:Sizes of input arguments…