Swift 中栈和堆的误解

Mar*_*v21 12 memory heap-memory stack-memory swift

我一直知道引用类型变量存储在堆中,而值类型变量存储在堆栈中。最近,我发现这张图说int、double、string等是值类型,而函数和闭包是引用类型: 在此输入图像描述

现在我真的很困惑。那么,当在类(又名引用类型)中定义整型、双精度型、字符串等时,它们保存在哪里?在同一类型中,当函数在结构体(又名值类型)中定义时,闭包保存在哪里?

Ita*_*ber 20

\n

我一直都知道引用类型变量存储在堆中,而值类型变量存储在堆栈中。

\n
\n

这在 Swift 中只部分正确。一般来说,Swift 不保证对象和值的存储位置,但以下情况除外:

\n
    \n
  1. 引用类型在内存中具有稳定的位置,因此对同一对象的所有引用都指向完全相同的位置,并且
  2. \n
  3. 值类型保证在内存中具有稳定的位置,并且可以根据编译器认为合适的情况任意复制
  4. \n
\n

从技术上讲,这意味着如果编译器知道在同一堆栈帧内创建和销毁对象且没有转义对其的引用,则对象类型可以存储在堆栈上,但实际上,您基本上可以假设所有对象都分配在堆栈上堆。

\n

对于值类型,情况有点复杂:

\n
    \n
  • 除非值需要基于位置的引用(例如,使用 来引用结构&),否则结构可能完全位于寄存器中:对小型结构进行操作可能会将其成员放置在 CPU 寄存器中,因此它甚至不会存在于内存中。Int(对于像s 和s这样的小型、可能短暂的值类型尤其如此Double,它们保证适合寄存器)
  • \n
  • 大值类型实际上进行堆分配:尽管这是 Swift 的实现细节,理论上将来可能会更改,但大于 3 个机器字的结构(例如,在 32 位机器上大于 12 个字节,或 24 个字节) 64 位机器上的字节)几乎可以保证被分配并存储在堆上。这与值类型的值性并不冲突:它仍然可以按照编译器的意愿任意复制,并且编译器在避免不必要的分配方面做得非常好
  • \n
\n
\n

那么,当在类(又名引用类型)中定义整型、双精度型、字符串等时,它们保存在哪里?

\n
\n

这是一个很好的问题,它触及了值类型的核心。考虑值类型存储的一种方法是内联,无论它需要在哪里。想象一个

\n
struct Point {\n    var x: Double\n    var y: Double\n}\n
Run Code Online (Sandbox Code Playgroud)\n

结构,它被布置在内存中。Point暂时忽略它本身是一个结构体的事实,xy相对于 存储在哪里Point?好吧,无论走到哪里都是内联的Point

\n
\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82   Point   \xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\n\xe2\x94\x82  x  \xe2\x94\x82  y  \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

当您需要存储 a 时Point,编译器会确保您有足够的空间来存储xy,通常一个紧接着另一个。如果aPoint存入栈,则xy依次存入栈;ifPoint存储在堆上,则x和作为的一部分y存在于堆上。无论 Swift 将 a 放在哪里,它总是确保您有足够的空间,并且当您分配给和 时,它们会被写入该空间。那在哪里并不重要。PointPointxy

\n

什么时候是另一个Point物体的一部分?例如

\n
\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82   Point   \xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\n\xe2\x94\x82  x  \xe2\x94\x82  y  \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

无论存储在何处,Then也都是内联Point布局的,并且它的值也都是内联布局的:

\n
\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82       Location       \xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\n\xe2\x94\x82          \xe2\x94\x82   Point   \xe2\x94\x82\n\xe2\x94\x82   name   \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\n\xe2\x94\x82          \xe2\x94\x82  x  \xe2\x94\x82  y  \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,当您创建一个Location对象时,编译器会确保有足够的空间来存储 aString和两个Doubles,并将它们依次放置。同样,它在哪里并不重要,但在本例中,它全部位于堆上(因为Location是引用类型,恰好包含值)。

\n
\n

反之,对象存储必须包含以下组件:

\n
    \n
  1. 用于访问对象的变量,以及
  2. \n
  3. 对象的实际存储
  4. \n
\n

假设我们Point从一个结构体变成了一个类。以前,直接Location存储内容Point,现在,它只存储对内存中实际存储的引用:

\n
\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90      \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82       Location       \xe2\x94\x82 \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x96\xb6\xe2\x94\x82   Point   \xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4 \xe2\x94\x82    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\n\xe2\x94\x82   name   \xe2\x94\x82   point \xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x98    \xe2\x94\x82  x  \xe2\x94\x82  y  \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98      \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

之前,当 Swift 布局空间来创建 时Location,它存储的是 1String和 2Double个 ;现在,它存储一个String和一个指向a 的指针Point。与 C 或 C++ 等语言不同,您实际上不需要知道它Location.point现在是一个指针,并且它实际上不会改变您访问对象的方式;但在幕后,尺寸和“形状”Location发生了变化。

\n

这同样适用于存储所有其他引用类型,包括闭包。保存闭包的变量很大程度上只是指向闭包的一些元数据的指针,以及执行闭包代码的方法(尽管具体细节超出了本答案的范围):

\n
\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90     \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82           MyStruct            \xe2\x94\x82     \xe2\x94\x82  closure  \xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4 \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x96\xb6\xe2\x94\x82  storage  \xe2\x94\x82\n\xe2\x94\x82  prop1  \xe2\x94\x82  prop2  \xe2\x94\x82  closure \xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x98   \xe2\x94\x82  + code   \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n