动态内存操作(2)

接上一篇文章http://t.csdn.cn/1ONDq,这次我们继续讲解关于动态内存的相关知识。

一、常见的动态内存错误

1.对NULL指针进行解引用操作

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main()
{int* p = (int*)malloc(INT_MAX/4);*p = 20;//如果没有足够的空间导致p为NULL,就会有问题//所以必须对malloc的返回值进行判断free(p);p = NULL;return 0;
}

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 11; i++){p[i] = 0;//原本只申请了十个整型的空间,但却访问十一个整型//所以造成越界访问}free(p);p = NULL;return 0;
}

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


int main()
{int a = 0;int* p = &a;free(p);//p不是动态开辟的空间,不能释放p = NULL;return 0;
}

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){p[i] = 0;p++;}//p++导致p不再指向这块空间的起始地址//所以如果释放p,等于释放这块空间的一部分(后五个整型空间)//这样就会出问题free(p);p = NULL;return 0;
}

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}free(p);//。。。。。free(p);//有时候头脑不清醒就可能释放多次,这样就会出问题return 0;
}

6.动态开辟内存后忘记释放内存(最常见)

即我们动态申请内存后,最后忘记用free释放了,这样就会造成内存泄漏

二、几个关于动态内存的经典例题

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

问:这段代码运行结果会是什么呢?

例题1解答:

一是没有释放动态内存;

二是会产生这样的错误:

原因是:因为在Test函数中,把str指针置空,然后作为参数传给GesMemory函数,该函数形参用指针p接收,这样指针p也为NULL,然后给指针p动态开辟空间,函数结束,到strcpy函数,但我们注意,我们是给指针p开辟空间,但指针str是没有变的,很多伙伴想不清楚,这不是传址调用吗,p指针变了,str指针也应该跟着变呀?

实则不然,我们应该注意参数是指针也不一定是传址调用,这里是指针之间赋值,应该同时上升一段层次,这里要二级指针才算传址调用,所以指针str是不会变的,还是NULL,既然是NULL,所以就没有足够的空间能放下strcpy的第二个参数,所以报错。

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

问:这段代码运行结果是什么?

例题2解答:

会产生这样的结果:

“很多小伙伴可能觉得,在GetMemory函数里面返回字符串的起始地址p,所以在Test函数里面用指针str来接收并打印,所以运行结果应该为打印字符串。”

但实则不然,我们一定要注意每个变量的生命周期,数组p的生命周期就只在函数GetMemory里面,所以当该函数return后,里面的变量所占的空间都会被自动销毁(释放),既然p的空间已经被释放了,还赋值给指针str,所以str就是个野指针,再打印str,就造成非法访问内存了。

这类问题属于:返回栈空间地址的问题

三、C/C++程序的内存开辟

如下图:

C/C++ 程序内存分配的几个区域:
1. 栈区( stack ):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区( heap ):一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
3. 数据段(静态区)( static )存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
实际上普通的局部变量是在 栈区 分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static 修饰的变量存放在 数据段(静态区) ,数据段的特点是在上面创建的变量,直到程序 结束才销毁 ,所以生命周期变长。

四、柔性数组

(一)、柔性数组的概念:

也许你从来没有听说过 柔性数组( flexible array 这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
如下:
//定义一:
struct S
{int a;int arr[0];
};
//定义二:
struct B
{int a;char b;int arr[];
};

(二)、柔性数组的特点:

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

(三)、柔性数组的使用:

如下例:
struct S
{int a;int arr[];
};int main()
{//动态开辟了4+40个字节,因为柔性数组是不会被sizeof计算的//前面四个字节是给成员a的,后面四十个字节给柔性数组//因为柔性数组的大小是未知的,我们只需给出预期大小struct S* str = (struct S*)malloc(sizeof(struct S) + 40);//检查if (str == NULL){perror("malloc");return 1;}//使用str->a = 10;int i = 0;for (i = 0; i < 10; i++){str->arr[i] = i + 1;//printf("%d ", str->arr[i]);}//用realloc扩容,因为柔性数组大小未知,是可以改变的//将之前柔性数组的10个字节的大小扩容到15个struct S* p = (struct S*)realloc(str, sizeof(struct S) + 60);//检查if (p == NULL){perror("realloc");return 1;}//使用str = p;for (i = 10; i < 15; i++){str->arr[i] = i + 1;}//打印for (i = 0; i < 15; i++){printf("%d ", str->arr[i]);}//释放free(str);str = NULL;return 0;
}

用malloc函数进行开辟空间,用realloc函数进行扩容,这样数组的大小就是可变的、柔性

的,这就是柔性数组的特点。

(四)、柔性数组的优势:

1.方便内存释放:
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free, 所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free 就可以把所有的内存也给释放掉。
2.这样有利于访问速度:
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)。

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

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

相关文章

Leetcode198. 打家劫舍

https://leetcode.cn/problems/house-robber/description/ 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&…

ubuntu18.04 OpenGL开发(显示YUV)

源码参考&#xff1a;https://download.csdn.net/download/weixin_55163060/88382816 安装opengl库 sudo apt install libglu1-mesa-dev freeglut3-dev mesa-common-dev 安装opengl工具包 sudo apt install mesa-utils 检查opengl版本信息&#xff08;桌面终端执行&#xff09…

CEC2013:CEC2013测试函数及多种智能优化算法求解CEC2013对比

一、CEC2013测试函数 CEC2013&#xff0c;该测试集合也是目前高质量论文应用较广泛的测试集&#xff0c;CEC2013测试集函数复杂&#xff0c;非常具有挑战力。 二、多种智能优化算法求解CEC2013 2.1 本文参与求解CEC2013的智能优化算法 本文选取一些经典的智能优化算法参与测…

喜讯 | 怿星科技获评SAE“优秀核心零部件企业”,测试软件平台工具广受赞誉

2023年9月22日-23日&#xff0c;SAE 2023汽车智能与网联技术国际学术会议成功举行。此次学术会议由SAE International与南昌智能新能源汽车研究院联合主办&#xff0c;大会汇聚了来自国内外智能网联领域的顶尖专家和学者。大会同期颁布的奖项旨在向行业推选出更多新时代涌现的杰…

领取我的国庆头像

一年一度的国庆节来了,祝大家节日快乐,本文教大家用Python绘制国庆专属头像。 文章目录 一、效果图二、实现代码一、效果图 这是把微信头像和红旗相结合制作出来的效果图:       如需图片和代码进行练习,可到公众号中发送“国庆头像”即可免费获取 二、实现代码 具体实…

深入理解JavaScript中的事件冒泡与事件捕获

在JavaScript中&#xff0c;事件是交互式网页开发中的关键概念之一。了解事件冒泡和事件捕获是成为一名优秀的前端开发者所必需的技能之一。本文将深入探讨这两个概念&#xff0c;解释它们是如何工作的&#xff0c;以及如何在实际应用中使用它们来处理事件。 一.什么是事件冒泡…

当两界交汇:前端开发、后端开发与全栈开发的对比与选择

编程世界就像一座大城市&#xff0c;前端开发和后端开发就像城市的两个不同街区。在这两个街区&#xff0c;前端和后端开发都有自己的价值和机会。 一、引言 有些人更喜欢在前端创造令人印象深刻的用户界面&#xff0c;而有些人更喜欢处理数据和系统逻辑。在选择时&#xff…

【VUE复习·4】计算属性computed:原理、完整写法(不常用)、与 methods 的区别、简写(最常用)、应用案例!

总览 1.简介计算属性 2.computed 与 methods 的区别 3.computed 的简写&#xff08;不修改计算属性&#xff0c;只显示&#xff09; 4.经典应用场景 一、计算属性 1.为什么需要计算属性&#xff1f; 首先&#xff0c;如果我们要写一个插值语法&#xff0c;而 {{ }} 内的内容…

idea 如何在命令行快速打开项目

背景 在命令行中从git仓库检出项目&#xff0c;如何在该命令行下快速用idea 打开当前项目&#xff0c;类似vscode 可以通过在项目根目录下执行 code . 快速打开当前项目。 步骤 以macos 为例 vim /usr/local/bin/idea 输入如下内容 #!/bin/sh open -na "IntelliJ IDE…

华为OD机试 - 判断字符串子序列(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述1、输入2、输出3、说明 四、Java算法源码五、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&am…

使用ElementUI结合Vue完善主页的导航菜单和书籍管理的后台数据分页查询

目录 一、动态树 ( 1 ) 数据表 ( 2 ) 后端 ( 2 ) 前端 二、书籍管理 数据表 后端 前端 ElementUI的背景 是一套基于Vue.js的桌面端组件库&#xff0c;由饿了么前端团队开发维护。它提供了丰富的UI组件和交互效果&#xff0c;可以帮助开发者快速构建出美观、易用的We…

(三)Python变量类型和运算符

所有的编程语言都支持变量&#xff0c;Python 也不例外。变量是编程的起点&#xff0c;程序需要将数据存储到变量中。 变量在 Python 内部是有类型的&#xff0c;比如 int、float 等&#xff0c;但是我们在编程时无需关注变量类型&#xff0c;所有的变量都无需提前声明&#x…

Activiz 9.2 for Linux Crack

Activiz 9.2 在 C#、.Net 和 Unity 软件中为您的 3D 内容释放可视化工具包的强大功能。 ActiViz 允许您轻松地将 3D 可视化集成到您的应用程序中。 ActiViz 功能 用 C# 封装的 3D 可视化软件系统 允许在 .NET 环境中快速开发可投入生产的交互式3D 应用程序 支持窗口演示基础 (…

Kafka:介绍和内部工作原理

展示Kafka工作方式的简单架构。 什么是Kafka&#xff1f;为什么我们要使用它&#xff1f;它是消息队列吗&#xff1f; •它是一个 分布式流处理平台或分布式 提交日志*。*•Kafka通常用于实时流数据管道&#xff0c;即在系统之间传输数据&#xff0c;构建不断流动的数据转换系统…

【GDB】 command 命令

GDB command 命令 语法 command 命令是一个很好用的调试命令&#xff0c;它配合断点使用&#xff0c;可以在指定的断点执行预先设置的命令 其语法为&#xff1a;command bread_id&#xff0c;这样会提示你输入你要执行的命令&#xff0c;以 end 结束。这个 bread_id 就是用 …

ElementUI之首页导航及左侧菜单(模拟实现)

目录 ​编辑 前言 一、mockjs简介 1. 什么是mockjs 2. mockjs的用途 3. 运用mockjs的优势 二、安装与配置mockjs 1. 安装mockjs 2. 引入mockjs 2.1 dev.env.js 2.2 prod.env.js 2.3 main.js 三、mockjs的使用 1. 将资源中的mock文件夹复制到src目录下 2. 点击登…

web前端tips:js继承——寄生式继承

上篇文章给大家分享了 js继承中的 原型式继承 web前端tips&#xff1a;js继承——原型式继承 今天给大家分享一下 js 继承中的 寄生式继承 寄生式继承 寄生式继承&#xff08;Parasitic Inheritance&#xff09;是一种基于原型式的继承方式&#xff0c;它通过创建一个仅用于…

聊聊wireshark的进阶使用功能 | 京东云技术团队

1. 前言 emmm&#xff0c;说起网络知识学习肯定离不来wireshark工具&#xff0c;这个工具能够帮助我们快速地定位网络问题以及帮助正在学习网络协议这块的知识的同学验证理论与实际的一大利器&#xff0c;平时更多的只是停留在初步的使用阶段。也是利用部门内部的网络兴趣小组…

使用YOLOv5的backbone网络识别图像天气 - P9

目录 环境步骤环境设置包引用声明一个全局的设备 数据准备收集数据集信息构建数据集在数据集中读取分类名称划分训练、测试数据集数据集划分批次 模型设计编写维持卷积前后图像大小不变的padding计算函数编写YOLOv5中使用的卷积模块编写YOLOv5中使用的Bottleneck模块编写YOLOv5…

maven中relativepath标签的含义

一 relative标签的含义 1.1 作用 这个<parent>下面的<relativePath>属性&#xff1a;parent的pom文件的路径。 relativePath 的作用是为了找到父级工程的pom.xml;因为子工程需要继承父工程的pom.xml文件中的内容。然后relativePath 标签内的值使用相对路径定位…