【C语言进阶(九)】常见内存错误以及柔性数组

news2024/11/24 16:58:22

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C语言学习分享⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多C语言知识
  🔝🔝


在这里插入图片描述


常见内存错误

  • 1. 前言
  • 2. 对NULL指针解引用操作
  • 3. 对动态开辟的空间越界访问
  • 4. free没有将空间完全释放
  • 5. 多次释放或忘记释放
  • 6. 经典笔试题目
    • 6.1 题目一
    • 6.2 题目二
  • 7. 柔性数组
    • 7.1 柔性数组的特点
    • 7.2 柔性数组的使用
    • 7.3 柔性数组的优势
  • 8. 总结以及拓展

1. 前言

本章重点:

本节着重讲解动态内存中的常见错误
并且分享几个经典的笔试题
并且介绍一个新概念: 柔性数组

动态开辟的内存在堆区,局部变量在栈区
它们的作用域什么时候销毁?
它们之间能不能相互关联起来使用?
包括一些错误的用法在面试中是常客!

在这里插入图片描述


2. 对NULL指针解引用操作

请看下面的代码:

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

解释:

malloc函数开辟空间失败会返回NULL
而每次使用完malloc后就应该判空
否则使用这个指针时不知道它是否为空

拓展:

现在的编译器很智能,电脑本身性能也强
malloc一般都不会失败
即使开辟五万个字节的空间
空间肯定也是会开辟成功的!
但是为了养成良好的习惯,应该记得判空

在这里插入图片描述
早在12年的时候,就有大佬回答:
堆区空间大约2GB
2GB=2×1024×1024×1024 个字节
暂且别说五万个字节的空间
就算是5亿个字节也完全能开辟出来


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

请看以下代码:

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

解释:

此处的越界访问和数组的越界访问类似
在开辟的空间外面的空间是未知的值
这里不做过多讲解


4. free没有将空间完全释放

请看以下代码:

void test()
{
  int *p = (int *)malloc(100);
  p++;
  free(p);//p不再指向动态内存的起始位置
}

解释:

这段代码可以这样理解:

在这里插入图片描述


5. 多次释放或忘记释放

请看以下代码:

int *p = (int *)malloc(100);
int *pp = (int *)malloc(100);//pp没有释放
 free(p);
 free(p);//重复释放

注意:
这里比较简单,但是值得注意的是
由于写代码时写着写着容易忘记释放空间
所以我建议在写完malloc的下面一条语句
直接写上free,再在它们中间写其他代码


6. 经典笔试题目

6.1 题目一

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

这段代码的问题是什么?

解释:

首先,Get函数的参数是char
str的类型也是char
,所以是传值传参
而形参p和实参str没有必然联系
改变形参p不会对str造成影响
所以应该传str的地址,用二级指针接受**

其次,这份动态开辟的空间没有释放
并且此时str还是NULL
使用strcpy相当于对NULL解引用会报错


6.2 题目二

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

这段代码有什么问题?

解释:

首先,p指向的空间是栈区开辟的
出Get函数后,这份空间就返给了系统
使用已经还给系统的空间是不对的!

其次,这段代码不会打印hello world
函数的栈帧的建立和销毁的过程
大致可以这样理解:

在这里插入图片描述

拓展:

函数栈帧的创建与销毁过程
可以参考这篇博客:

函数栈帧的创建与销毁


7. 柔性数组

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

比如:

struct NEO
{
  int i;
  int a[0];//柔性数组成员
  //int a[];或者使用这种写法
}

7.1 柔性数组的特点

基本特点:

  • 柔性数组前面至少有一个成员

  • 柔性数组必须是最后一个成员

  • 计算结构体大小时
    柔性数组不计算在内

  • 为拥有柔性数组的结构体开辟空间时
    除了结构体大小外还要加上数组大小

例如:

struct NEO
{
 int i;
 int a[0];//柔性数组成员
}
printf("%d\n", sizeof(struct NEO));

打印4
这里只会计算成员i的大小
而柔性数组的大小不算在内


7.2 柔性数组的使用

先定义一个柔性数组:

struct NEO
{
  int i;
  char ch;
  int a[0];//柔性数组成员
}

假设想让数组a有10个整型的空间

可以这样开辟空间:

struct NEO* p = (struct NEO*)malloc(sizeof(struct NEO)+10*sizeof(int));

这段代码可以这样理解:

在这里插入图片描述


7.3 柔性数组的优势

在定义结构体时,假设使用正常的指针p

struct NEO
{
  int i;
  int* p;
  char ch;
}

struct NEO* n;

然后为指针p动态开辟一份空间
首先要为结构体变量开辟空间

n = (struct NEO*)malloc(sizeof(struct NEO))
n->p = (int*)malloc(sizeof(int)*10);

然而使用这个变量后,需要free掉空间
malloc了两次空间,所以需要释放两次

先释放谁? 当然是内部是指针p!
如果先把结构体变量n释放了
那么就找不到内部的指针p了

发现不使用柔性数组的话,很麻烦
并且很容易将释放顺序搞反

总结柔性数组的优势:

  1. 好处一:方便内存释放

  2. 好处二:有利于访问速度

使用柔性数组时,数组和其他成员的内存
是连续的而使用指针时,内存不连续


8. 总结以及拓展

使用动态开辟空间解决问题固然方便
但是稍不注意就会出现内存问题
这是一把双刃剑,使用应谨慎

拓展:柔性数组的用处

可以参考这篇文章:

开发使用方式之柔性数组

柔性数组拓展阅读:

结构体中的成员数组和指针

在这里插入图片描述


🔎 下期预告:文件操作 🔍

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

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

相关文章

伦敦银价格一览表

一目均衡表&#xff08;Ichimoku Kinko Hyo&#xff09;是日本在二次大战前发明的行情分析方法&#xff0c;是全世界技术分析的鼻祖&#xff0c;其功能是提供市场的方向及入市位&#xff0c;被广泛应用于股市、债市和贵金属市场之中。日文KINKO的意思为时空平衡点&#xff0c;H…

Blueprint —— 入门笔记2

BP_Character Animation BlueprintBlend Space 1D 角色按键动画 命令slomo 0.1减慢时间&#xff0c;用于测试&#xff1b; 玩家输入&#xff1a;位移、旋转、缩放&#xff1b; 碰撞检测&#xff1a;执行动作等&#xff1b; 游戏开始 地图&#xff0c;开始游戏页面 打开地图 输入…

用于视觉跟踪的在线特征选择研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

如何在Appium中使用AI定位

当我们在写自动化测试脚本的时候&#xff0c;传统情况下一定要知道元素的属性&#xff0c;如id、name、class等。那么通过AI的方式定位元素可能就不需要知道元素的属性&#xff0c;评价人对元素的判断来定位&#xff0c;比如&#xff0c;看到一个搜索框&#xff0c;直接使用ai:…

Tomcat安装及配置教程(IDEA整合Tomcat)

目录 友情提醒第一章、Tomcat下载与安装1.1&#xff09;Tomcat介绍1.2&#xff09;官网下载 第二章、Tomcat配置环境变量2.1&#xff09;windows环境变量配置2.2&#xff09;验证Tomcat配置是否成功2.3&#xff09;报错解决 第三章、IDEA整合Tomcat3.1&#xff09;打开IDEA开发…

11.Ceph 对象存储系统 RGW 接口

文章目录 Ceph 对象存储系统 RGW 接口概念逻辑单位创建RGW接口开启httphttps创建RadosGW账户S3接口访问测试 Ceph 对象存储系统 RGW 接口 概念 对象存储&#xff08;object storage&#xff09;是非结构数据的存储方法&#xff0c;对象存储中每一条数据都作为单独的对象存储&…

资深测试总结,自动化测试-JSON+YAML+CSV+Excel数据驱动(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 数据驱动 在自动…

三分钟为你揭晓什么软件可以音频转文字

在一个繁忙的国际会议上&#xff0c;艾丽莎是一位积极参与的会议记录员。她负责记录并整理与会者的发言内容&#xff0c;以便后续审阅和分析。然而&#xff0c;由于会议中使用英语进行交流&#xff0c;艾丽莎常常面对着大量的英文音频文件&#xff0c;需要将这些音频转换为文字…

C# 连接mysql数据库报错:Character set ‘utf8mb3‘ is not supported by .Net Framework.

最近项目突然连接mysql数据库出现一个bug&#xff0c;排查了半小时&#xff0c;最后更新MySql.Data版本解决了&#xff0c;错误信息如下&#xff1a; System.NotSupportedException: Character set utf8mb3 is not supported by .Net Framework.在 MySql.Data.MySqlClient.Cha…

C#List转IList方法

最近工作中使用到了C#的List和IList。 这里参考百度上的资料&#xff0c;总结一下。 IList使用命名空间&#xff1a; using System.Collections; List<T>类:表示可通过索引访问的对象的强类型列表&#xff0c;提供用于对列表进行搜索、排序和操作的方法。 IList<T&…

思维导图软件哪个好?试用百款导图软件只留下这15个

​思维导图软件哪个好&#xff1f; 这是许多第一次使用思维导图软件的朋友无法回避的问题&#xff0c;看着眼前有数百款思维导图软件&#xff0c;数量多到让人患上选择困难症&#xff0c;不知道要选哪个导图软件好&#xff0c;这就好比在繁星点点的夜空中寻找属于自己的那颗星&…

vue3实现上传功能

效果&#xff1a; 点击即可上传 代码&#xff1a; <a-form-item label"附件" name"logUrl" placeholder""><a-uploadv-model:file-list"filesLists":customRequest"uploadFile"class"upload-list-inline&quo…

基于PyQt5的UI界面开发——对基本控件的介绍

基本控件介绍 在PyQt中&#xff0c;控件是用户界面上的可见元素。控件可以包括按钮、标签、文本框、进度条等。每个控件都有自己的属性和方法&#xff0c;可以通过编程方式进行调整和操作。 以下是一些常用的PyQt控件&#xff1a; QLabel&#xff08;标签&#xff09;&#…

WebDAV之π-Disk派盘 + Keepass

KeePass是一款优秀的开源密码管理器,可以帮助用户安全、高效地管理自己的密码,而派盘则是一款本地个人云存储解决方案,可以帮助用户轻松地存储、管理和共享各种类型的文件。将这两个工具结合在一起,可以让用户更加安全和便捷地管理自己的密码。 π-Disk派盘 – 知识管理专…

k8s单机部署mysql

前面我们学习了k8s入门系列文章&#xff0c;了解了k8s的一些基础概念以及怎么使用。本篇文章将进行一个小小的实战&#xff0c;使用k8s来部署单机版的mysql数据库&#xff0c;基本涵盖到前面讲到的Namespace、Pod、Deployment、Service、PV、PVC、Secret等资源对象。 我们先画…

【力扣每日一题】2023.7.17 字符串相加

题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题面很简单,就是要将两个字符串看作是数字然后相加,将最后结果转为字符串返回即可. 看到这题我的第一反应是直接转成数字再相加再转成字符串,像是这样: class Solution { public:string addStrings(string num1, string…

无畏契约进不去,提示图形驱动程序崩溃怎么办?

无畏契约国内开服后&#xff0c;不少玩家表示&#xff0c;外服一点毛病没有&#xff0c;怎么在国服一下又是挂机违规&#xff0c;一下子又是图形驱动程序崩溃的警告呢&#xff1f;再说了&#xff0c;人有三急&#xff0c;逼不得已掉个线&#xff0c;再次连上就再也进不去了&…

Redis进阶底层原理 - 客户端集群方案

Redis集群一般来说会存在多个主节点&#xff0c;用于数据分区。对于客户端来说只会连接到某一个Redis主机节点。那客户端如果使用集群&#xff1f;方案如下&#xff1a; 原图地址&#xff1a;

reggie优化03-MySQL主从复制

1、介绍 2、配置 1、开启CentOS7克隆2台 2、用Navicat连接2个数据库 3、进入shell&#xff0c;配置主库 验证&#xff1a; 4、进入shell&#xff0c;配置从库 3、测试主从复制 在主库创建一个数据库&#xff0c;从库也会自动生成一个数据库&#xff08;表&#xff0…

接口测试和单元测试

接口测试的本质&#xff1a;就是通过数据驱动&#xff0c;测试类里面的函数。单元测试的本质&#xff1a;通过代码级别&#xff0c;测试函数。单元测试的框架&#xff1a;unitest接口&#xff0c;pytestWEB----->接口&#xff0c;pytestjenkinsallure。 requests 模块讲解和…