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

💓博主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.hqwc.cn/news/27932.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】list的使用

今天我们来进入到C另一个容器list的学习 目录 一、list的介绍 二、list的使用 2.1 构造函数 2.2 迭代器函数接口 2.3 容量函数接口 2.4 元素访问函数接口 2.5 常用修改函数接口 2.6 常用操作函数接口 一、list的介绍 文档介绍&#xff1a;list - C Reference (cpluspl…

html+JavaScript实现一个好看的颜色码查询器,支持查询、转换、颜色选择器和颜色码对照表

前言 相信大家平时工作的时候应该会经常用到颜色码吧&#xff0c;比如说想找个好看的颜色&#xff0c;或者有个颜色码但是不知道这个码是什么颜色的&#xff0c;这个时候我们就可以用颜色码对照表或者颜色码查询来查看了。 当然也可以用截图软件或者取色器或者PS来查看&#…

安卓设备监听全部输入信号

前言&#xff1a; 最近团队收到一个产品需求&#xff0c;需要监听安卓设备上用户是否有输入行为&#xff0c;以免定制推荐的时候打搅到用户。这里指的是设备上所有应用的输入行为&#xff0c;而不是单指某一个应用。 这个需求还是蛮有挑战性的&#xff0c;需要涉及到很多FW层…

vue新增删除内容排序问题解决处理

本次答题选项的删除添加是个人最初比较头疼的地方。比如ABCD四个选项&#xff0c;删除c选项后&#xff0c;点击【新增答题类型】选项按钮&#xff0c;则默认创建是E选项。再或者就是ABCD四个选项位置删除任意一个后&#xff0c;顺序被打乱等&#xff0c;最后解决了&#xff0c;…

【状态估计】基于UKF法、AUKF法的电力系统三相状态估计研究(Matlab代码实现)

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

【项目 进程2】2.3 进程创建 2.4父子进程虚拟地址空间 2.5GDB多进程调试

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 2.3 进程创建2.4 父子进程虚拟地址空间父子进程之间的关系&#xff1a; 2.5 GDB多进程调试 2.3 进程创建 系统允许一个进程创建新进程&#xff0c;新进程即为子进程…

Vue2 ➔ Vue3 都做了哪些改变?

不是吧&#xff0c;兄弟&#xff0c;Vue3 都出来多久了&#xff0c;你还对这个感兴趣&#xff0c;说&#xff01;是不是没好好卷&#xff1f;&#x1f60f; 俺也一样 &#x1f602;&#xff0c;Vue3 出来之后只是简单了解了一下&#xff0c;然后还是转头一直在写 Vue2。当然&a…

基于weka手工实现多层感知机(BPNet)

一、BP网络 1.1 单层感知机 单层感知机&#xff0c;就是只有一层神经元&#xff0c;它的模型结构如下1&#xff1a; 对于权重 w w w的更新&#xff0c;我们采用如下公式&#xff1a; w i w i Δ w i Δ w i η ( y − y ^ ) x i (1) w_iw_i\Delta w_i \\ \Delta w_i\eta(y…

Maven —— 项目管理工具

前言 在这篇文章中&#xff0c;荔枝会介绍如何在项目工程中借助Maven的力量来开发&#xff0c;主要涉及Maven的下载安装、环境变量的配置、IDEA中的Maven的路径配置和信息修改以及通过Maven来快速构建项目。希望能对需要配置的小伙伴们有帮助哈哈哈哈~~~ 文章目录 前言 一、初…

设计模式-组合模式在Java中的使用示例-杀毒软件针对文件和文件夹进行杀毒

场景 组合模式 组合模式(Composite Pattern)&#xff1a; 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。 组合模式对单个对象&#xff08;即叶子对象&#xff09;和组合对象&#xff08;即容器对象&#xff09;的使用具有一致性&#xff0c; 组合模式…

排序算法之冒泡排序详解-python版

冒泡排序&#xff1a;通过比较2个相邻元素之间的大小&#xff0c;交换元素顺序&#xff0c;从而达到排序目的。 从百度百科摘抄下来的冒泡排序原理如下&#xff1a; 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。 对每一对相邻元素做同样的工作&#xf…

elementUI 非表单格式的校验

在普通表单中对输入框、选择框都有校验案例。 但是在自定义非空中如何进行校验官网并没有说明 关键代码 clearValidate 方法清除校验 this.$refs.formValue.clearValidate(signinimg) 使用案例 <template><div class"stylebg"><Tabs icons"el-…