【QandA C++】内存泄漏、进程地址空间、堆和栈、内存对齐、大小端和判断、虚拟内存等重点知识汇总

目录

内存泄漏

内存模型 、进程地址空间

堆和栈的区别

内存对齐

大端小端及判断

虚拟内存有什么作用


内存泄漏

概念:

是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况, 内存泄漏并不是指内存在物理上的消失, 而是应用程序分配了某段内存后, 因为设计错误, 失去了对该段内存的控制, 因而造成了内存的浪费.

  1. new和malloc申请资源使用后, 没有用delete和free释放
  2. 子类继承父类时, 父类的析构函数不是虚函数
  3. 未关闭的文件或资源

危害:

长期运行的程序出现内存泄漏, 影响很大; 出现内存泄漏会导致响应越来越慢, 最终卡死.

避免:

  • 计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
  • 一定要将基类的析构函数声明为虚函数
  • 对象数组的释放一定要用delete []
  • 有new就有delete,有malloc就有free,保证它们一定成对出现
  • 出问题了使用内存泄漏工具检测。

内存泄漏非常常见,解决方案分为两种:

  1. 事前预防型。如智能指针等。
  2. 事后查错型。如泄漏检测工具。
  • Linux下可以使用Valgrind工具
  • Windows下可以使用CRT库

内存模型 、进程地址空间


如上图,从低地址到高地址,用户空间内存,从低到高分别是 6 种不同的内存段:

  • 代码段,包括二进制可执行代码。只读,包含一些只读的变量
  • 数据段,包括已初始化的静态常量和全局变量;
  • BSS 段,包括未初始化的静态变量和全局变量;
  • 堆段,包括动态分配的内存,从低地址开始向高地址增长;动态申请内存用,由new分配的内存块,其释放由程序员控制(一个new对应一个delete)
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长;最后还有一个共享区,位于堆和栈之间。
  • 栈段,存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。在不需要时自动清除的存储区。

Linux下的进程地址空间具体是由mm_struct实现的

struct mm_struct{unsigned int code_start;unsigned int code_end;unsigned int init_start;unsigned int init_end;unsigned int uninit_start;unsigned int uninit_end;unsigned int heap_start;unsigned int heap_end;unsigned int stack_start;unsigned int stack_end;
};

比如堆向上增长,栈向下增长,实际上就是改变mm_struct中的堆和栈的起始指针来实现的!

为什么要有进程地址空间?

为了实现多任务操作系统中的多进程隔离和独立运行,以确保不同进程之间的互不干扰和安全性。

  1. 隔离和保护:每个进程都有自己独立的地址空间,使得不同进程之间的内存互不干扰。这种隔离性确保了一个进程的错误或恶意行为不会对其他进程造成影响,提高了系统的稳定性和安全性。
  2. 相对地址一致性:每个进程都认为自己的地址空间是从0开始的,并且认为看到的是相同的地址空间范围。这种相对地址一致性使得进程可以使用相对地址进行内存操作,而不必关心其他进程的地址空间。
  3. 独立内存:每个进程都认为自己独占内存,可以自由分配和管理自己的内存资源。这使得进程可以在不互相干扰的情况下运行,并且不需要担心其他进程的内存使用情况。
  4. 虚拟内存:进程地址空间还支持虚拟内存的概念,允许操作系统在物理内存有限的情况下为每个进程提供大于物理内存的虚拟内存空间。这通过将部分数据存储在磁盘上,根据需要进行页面调度,提高了内存利用率。

堆和栈的区别

分配方式:

栈由编译器自动分配和管理,程序员无需手动控制栈内存的分配和释放。局部变量、函数参数以及函数调用上下文等都存储在栈上。

堆由程序员手动申请和释放,通常使用new(C++)或malloc(C)等函数分配内存,并使用delete(C++)或free(C)来释放内存。堆用于存储动态分配的数据,如动态数组、对象实例等。

空间大小限制:

栈的大小通常是有限的,具体大小由编译器或操作系统设置。栈的大小在编译时或运行时可以进行配置,但总是有限的。

堆的大小受限于系统可用的虚拟内存大小,通常比栈要大得多。堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大

栈空间和堆区的大小是由操作系统和编译器决定的,不同系统和编译器可能会有不同的默认值。一般来说,栈空间默认是1MB或2MB,而堆区一般是1GB到4GB之间。

内存管理机制:

栈的内存管理由编译器自动完成,变量的生命周期与其作用域相对应。栈内存的分配和释放是隐式的,不需要程序员干预。

堆的内存管理由程序员手动控制。程序员负责显式地分配堆内存,并在不再需要时释放它。如果不正确地管理堆内存,可能会导致内存泄漏或悬挂指针等问题。

碎片问题:

栈内存通常不会出现碎片问题,因为栈的内存分配和释放都是线性的,按照函数调用的顺序进行。

堆内存可能会出现碎片问题,特别是在频繁进行动态内存分配和释放操作时。这可能导致内存空间的不连续,影响程序的性能。

生长方向:

栈的生长方向通常是向下的,即从高地址向低地址增长。这意味着栈的顶部在分配时逐渐向较低的地址移动。

堆的生长方向通常是向上的,即从低地址向高地址增长。堆内存在动态分配时逐渐向较高的地址分配。

分配效率:

栈由编译器和操作系统提供的支持,分配和释放内存的效率较高,通常采用硬件级别的指令进行操作。

堆的内存分配和释放需要程序员显式地调用函数,效率相对较低,并可能涉及复杂的内存管理机制。

形象的比喻

栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

内存对齐

内存对齐涉及到如何存储和访问数据以提高计算机的性能和效率。确保数据按照一定的规则存储在内存中,以便于有效地访问

结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
  2. 其他成员变量要对齐到最小对齐数的整数倍的地址处。
  3. 结构体的总大小为最大对齐数的整数倍

对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。

注:VS中的默认对齐数为8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。

要修改编译器的默认对齐数,我们需要借助于以下预处理命令:#pragma pack(...)

为什么存在内存对齐?

平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。

  • 比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。

性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次。

其实结构体的内存对齐是拿空间来换取时间的做法

大端小端及判断

大端模式:是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中;地址由小向大增加,而数据从高位往低位放。

小端模式:是指数据的高位(就是权值较大的前面那几位)保存在内存的高地址中,而数据的低位,保存在内存的低地址中;地址由大向小增加,而数据从低位往高位放 。

大小端的意义在于确保数据在不同的计算机体系结构之间正确传递、解释和处理,保证系统之间的互操作性和数据的可移植性。

例如:32bit的数字0x12345678

所以在Socket编程中,往往需要将操作系统所用的小端存储的IP地址转换为大端存储,这样才能进行网络传输

端模式中的存储方式为:

端模式中的存储方式为:

如何在代码中进行判断呢?

方式一:使用强制类型转换-这种法子不错

#include <iostream>
using namespace std;
int main()
{int a = 0x1234;//由于int和char的长度不同,借助int型转换成char型,只会留下低地址的部分char c = (char)(a);if (c == 0x12)cout << "big endian" << endl;else if(c == 0x34)cout << "little endian" << endl;
}

方式二:巧用union联合体

#include <iostream>
using namespace std;
// union联合体的重叠式存储,endian联合体占用内存的空间为每个成员字节长度的最大值
union endian
{int a;char ch;
};
int main()
{endian value;value.a = 0x1234;//a和ch共用4字节的内存空间if (value.ch == 0x12)cout << "big endian"<<endl;else if (value.ch == 0x34)cout << "little endian"<<endl;
}

虚拟内存有什么作用

  • 虚拟内存可以使得进程对运行内存超过物理内存大小
  • 因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。当进程需要访问被置换出去的页时,它们会被重新加载到物理内存中。这种机制允许了更大的程序运行,而不受物理内存的限制。
  • 提高内存利用率:
  • 虚拟内存系统可以更好地利用物理内存资源。只有进程当前需要的部分内存被加载到物理内存中,而不是将整个程序加载到内存中。这减少了内存浪费,允许多个进程在有限的物理内存中共存。
  • 虚拟内存为每个进程提供了独立的地址空间
  • 每个进程有自己的页表,这样,一个进程无法直接访问其他进程的内存,从而提高了安全性和隔离性。即使两个进程使用相同的虚拟地址,它们映射到不同的物理内存位置。
  • 页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。

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

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

相关文章

Docker 安装Redis(集群)

3主3从redis集群配置 1、新建6个docker容器 redis 实例 docker run -d --name redis-node-1 --net host --privilegedtrue -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381 docker run -d --name redis-node-2 --ne…

2023 “华为杯” 中国研究生数学建模竞赛(E题)深度剖析|数学建模完整代码+建模过程全解全析

​ 问题一 血肿扩张风险相关因素探索建模 思路&#xff1a; 根据题目要求,首先需要判断每个患者是否发生了血肿扩张事件。根据定义,如果后续检查的血肿体积比首次检查增加≥6 mL或≥33%,则判断为发生了血肿扩张。 具体判断步骤: (1) 从表1中提取每个患者的入院首次影像检查…

python基础语法

目录 常量和表达式 变量和类型 1.整数int 2.小数float 3.字符串string 4.布尔类型bool 5.动态类型 注释 输入输出 输出 输入 运算符 算术运算符 关系运算符 逻辑运算符 赋值运算符 python和C、Java语法区别 创建一个python项目 常量和表达式 在python中&…

String的增删查【C++】

String的增删查【C】 前言string的增删查改构造与析构构造string(const char* str "")赋值构造string(const string& s1) 赋值重载析构函数增reservepush_backappendinsert 删erase 查迭代器流插入流提取流插入流提取 前言 从这里开始可以算是进入了STL的学习中…

CRM客户管理系统英文专业版

外资公司日常沟通的语言以英文为主&#xff0c;业务往来也是涉及到国内外&#xff0c;专业的英文版CRM系统很适合这样的业务团队&#xff0c;尤其CRM供应商是国际化企业&#xff0c;在海外也有分公司、办事处。 多语言 ZOHO支持多语种如英语、汉语、日语等28种语言&#xff0…

MySQL基础篇-函数

目录 1.字符串函数 2.数值函数 3.日期函数 4.流程函数 5.小结 在MySQL中&#xff0c;函数是一种数据库对象&#xff0c;用于执行特定的操作或计算&#xff0c;并返回结果。函数通常用于查询、数据处理和转换&#xff0c;以及在SQL语句中执行其他操作。MySQL提供了许多内置函…

linux驱动之input子系统简述

文章目录 一、什么是input子系统二、内核代码三、代码分析 一、什么是input子系统 Input驱动程序是linux输入设备的驱动程序&#xff0c;我们最常见的就按键&#xff0c;触摸&#xff0c;插拔耳机这些。其中事件设备驱动程序是目前通用的驱动程序&#xff0c;可支持键盘、鼠标…

C++ -- IO流

目录 C语言的输入与输出 CIO流 C标准IO流 C文件IO流 文件常见的打开方式如下 以二进制的形式操作文件 以文本的形式操作文件 读写结构体 stringstream的简单介绍 C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输…

零基础学空手道_3_空手道的站姿(上)

欢迎回来一起学习刚柔流空手道。 讲一些比较严肃的内容&#xff0c;就是礼仪和站姿。 空手道一开始不是要学习怎么打&#xff0c;而是要学习怎么去尊重别人和不打。所以礼仪很重要。 一切事情都是以礼仪开始&#xff0c;以礼仪结束。这叫以理始以理终。 空手道也是这样&#xf…

MySQL - DML数据增删改

功能介绍&#xff1a; DML&#xff08;Data Manipulation Language&#xff09;数据操作语言&#xff0c;用来对数据库中表的数据记录进 行增、删、改操作。 添加数据&#xff08;INSERT&#xff09; 基本语法&#xff1a;insert into 表名(字段列表) values (值列表); …

【问题解决】Android Studio 无法连接手机(荣耀90)无法识别手机usb

问题描述&#xff1a; 使用AS调试的时候遇到一个问题&#xff0c;由于是重装后的电脑&#xff0c;什么都没配置&#xff0c;但是两个旧手机都在安装SDK tools里的Google usb driver后直接连上AS&#xff0c;而我的新手机却死活连不上&#xff0c;查了一下午&#xff0c;啥方法都…

Redis原理(一):Redis数据结构(上)

文章目录 1、 Redis数据结构-动态字符串2、 Redis数据结构-intset3、 Redis数据结构-Dict4、 Redis数据结构-ZipList5、 Redis数据结构-ZipList的连锁更新问题6、 Redis数据结构-QuickList1、 Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串,value往往是字符串…

MongoDB(二)基础操作 创建、删除,查询等

mongodb有一个特点&#xff0c;如果某个库&#xff0c;库下面没数据&#xff08;mongodb成集合&#xff09;&#xff0c;该库等于不存在的 mongodb只要创建一个库&#xff0c;在库下写入数据&#xff0c;该库才会生成 mongoshe [-hhost -pxxx] 创建数据库 use 数据库名 # 如果…

c语言常见字符函数、内存函数(详讲)

前言&#xff1a; 其实在c语言当中是没有字符串这一概念的&#xff0c;不像c里面有string类型用来存放字符串。在c语言中我们只能把字符串放在字符串常量以及字符数组中。 1.常见字符串函数 1.1strlen size_t strlen ( const char * str );作用&#xff1a;用来求字符串中 …

人工智能的未来:从 Jetson 到 GPT,沙龙见闻与洞察

前言 在当今数字化时代&#xff0c;人工智能正以惊人的速度改变着我们的生活和工作方式。从智能语音助手到自动驾驶汽车&#xff0c;从智能家居到医疗诊断&#xff0c;人工智能技术已经广泛渗透到各个行业&#xff0c;并为其带来了巨大的变革和创新。越来越多的行业专家、学者…

postman发送图片

POSTMAN 如何发送携带图片的请求? 闲话不叙 步骤如下&#xff1a; 新建一个请求&#xff0c;在Headers中添加一对k-v : Content-Type > multipart/form-data 请求的接口: RequestMapping("/fileUploadController")public String fileUpload(MultipartFile fil…

【C++】构造函数和析构函数第一部分(构造函数和析构函数的作用)--- 2023.9.25

目录 前言初始化和清理的概念构造函数和析构函数的作用构造函数的作用析构函数的作用 使用构造函数和析构函数的注意事项默认的构造函数和析构函数结束语 前言 在使用c语言开发的项目场景中&#xff0c;我们往往会遇到申请空间的需求&#xff0c;同时也肯定遇到过程序运行一段…

积跬步致千里 || 可视化动图展示

可视化动图展示 目前只能在 jupyter notebook 中测试成功 %matplotlib notebook import numpy as np import matplotlib.pyplot as plt import timen 500 data np.random.normal(0,1,n)fig plt.figure() ax fig.add_subplot(111)fig.show() fig.canvas.draw()for i in ra…

什么是Redux?它的核心概念有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是Redux&#xff1f;⭐ 它的核心概念有哪些&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发…

C++——模板

目录 泛型编程 函数模板 函数模板概念 函数模板格式 类模板 类模板的定义格式 类模板的实例化 泛型编程 泛型编程是什么呢&#xff1f;泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。型就是类型&#xff0c;不…