跳至主要內容

内存管理

LincZero大约 5 分钟

内存管理

术语

高地址、低地址

  • 基本概念不解释。数组指针 (new出来的内存,在堆上) 自增就是从低地址到高地址。

高位、低位

  • 和10进制的高低位没什么区别。例如数字 1024,左边的例如1就是高位,右边的4就是最低位

先读

  • 地址的先读:计算机先读小地址再读大地址。处理字节序的时候,他并不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节
  • 位的先读:对于大端字节序,先读到的就是高位字节,后读到的就是低位字节,小端字节序正好相反
    • 对于计算,先读到低位字节是更好的。这便是小端,也是计算时cpu的读法 (人类的计算是从左到右看一个数字,然后跳到这个数的最右边再进行运算。对计算机来说,也不是不能采取一样的策略,但这就慢了)
    • 对于符号判断,先读到高位字节是更好的,这便是大端,也是读字时人类的读法

大端、小端

  • Big-Endian(大端模式)
    • 特点:高位字节排放在内存的低地址端,这是人类读写数值的方法
    • 优点
      • 符号位在所表示的数据的内容的第一个字节中,便于快速判断数据的正负和大小
      • 人类可读
    • 应用:网络传输、文件储存
  • Little-Endian(小端模式)
    • 特点:低位字节排放在内存的低地址端,即以0x1122形式储存
    • 优点
      • 计算更快
      • 强制转换时。不需要调整字节的内容
    • 应用:计算机内部处理 (计算)

大端视图、小端视图

这两种视图与大小端无关,无论是人还是机器都是从低地址向高地址读

  • 正序视图:左上小地址,右下大地址。

    • 应用
      • 查看使用大端存储的东西时,都会使用这一视图。优点是配合大端存储,可读性好,可以直接看出ascii字符串
      • Windows使用Hex软件模式打开文本
      • RFC网络协议图
      • 使用Wireshark打开Pcap
      • CLion/VS调试器中的"内存窗口"
  • 倒序视图:上大地址,下小地址

    • 应用
      • 画虚拟内存图
      • (小端存储机器上) 0x123456 / 0b0011001100110011 这种字面量写法
  • 我觉得倒序视图的画法一般是为了兼容教材,感觉没什么用,两种视图的频繁切换会给学习带更多困惑,感觉全部采用正序视图来画会更好。

    不然你存储的内容倒过来了,我视图也一起倒过来了。看上去内容就不变了,太混乱了

    而且你堆指针++是往右往下,在CLion/VS的内存查看器中查看堆时,不也是正序视图么,怎么图就给画成倒序视图了

虚拟内存

先来复习一下虚拟内存

img
img

Q:虚拟内存图中,为什么内核空间在高地址而非低地址呢?虚拟内存的数量不应该有很多吗,为什么还会有内核空间?

A:首先,要明白虚拟内存的概念。虚拟内存是一种内存管理技术,它使得每个进程都觉得自己独占了全部的物理内存,进程所见的内存地址并不是实际的物理地址,而是虚拟地址,通过内存管理单元(MMU)进行转换,映射到实际的物理内存中。

内核空间在高地址是因为操作系统需要保护内核代码和数据不被用户进程访问,所以将其放在高地址处。在虚拟内存中,地址空间被划分为用户空间内核空间,用户空间供用户程序使用,内核空间供操作系统内核使用。

虽然虚拟内存的容量很大(例如32位系统中为4GB),但是这并不代表这些全部都可以被用户程序使用。因为操作系统内核也需要一部分空间来存放内核代码和数据,所以会有内核空间的存在。

在一般的操作系统中,虚拟内存空间的布局通常是用户空间在低地址,内核空间在高地址。这样做有几个好处:

  1. 能够保护内核代码和数据不被用户程序直接访问,提高系统的安全性。
  2. 用户程序的地址空间可以动态增长(例如堆和栈的动态扩展),而内核空间的大小则是固定的。
  3. 有助于提高内存的使用效率,因为用户程序通常只会使用到部分地址空间,剩余的空间可以由内核使用。

字节序 (大小端)

参考:

img
img
img
img

补充

  • 全局变量:先定义的全局变量位于低地址,后定义的位于高地址。
  • 栈中变量:由于栈是往低地址生长的,所以先声明的变量位于高地址
  • 堆中变量:由于堆是往高地址生长的,所以先声明的变量位于低地址。

临时对象的生命周期

参考:B站up,mq白cpp

cpp的临时对象存在于整个表达式中

auto& t = foo("string1");	// 行1,等同于 foo(std::string("string1")),生成了一个临时的字符串对象,调用了构造函数
...			 		// 行2,到了这一行,上面的字符串对象会被析构。在此之前,也就是在foo函数内部返回后,t拿到值时,该字符串对象还是存在的

在 zh.cppreference.com/w/cpp/language/reference_initialization 中的解释是这样的:

在函数调用中绑定到函数形参的临时量,存在到含这次函数调用的全表达试结尾为止:如果函数返回一个生命长于表达式的引用,那么它会称为悬垂引用。