Swift堆栈/堆理解

Dán*_*agy 33 memory heap stack ios swift

我想在swift中不希望存储在堆栈/堆中的内容.我有一个很好的估计:你打印的所有东西和内存地址都不是值,它们存储在堆栈中,而是打印出的值是什么,它们在堆上,基本上根据值和引用类型.我完全错了吗?并且可选地,您是否可以提供堆栈/堆的可视化表示?

Jay*_*yas 53

正如juul所述,引用类型存储在堆中,而值存储在堆栈中......

在这里,我想解释为什么......

堆栈和堆?

堆栈用于静态内存分配,堆用于动态内存分配,两者都存储在计算机的RAM中.

在堆栈上分配的变量直接存储到存储器中,并且对该存储器的访问非常快,并且在编译程序时处理它的分配.当函数或方法调用另一个函数,该函数又调用另一个函数等时,所有这些函数的执行将保持挂起,直到最后一个函数返回其值.堆栈始终以LIFO顺序保留,最近保留的块始终是要释放的下一个块.这使得跟踪堆栈非常简单,从堆栈中释放块只不过是调整一个指针.

在此输入图像描述

在堆上分配的变量在运行时分配了内存并且访问此内存有点慢,但堆大小仅受虚拟内存大小的限制.堆的元素彼此之间没有依赖关系,并且可以随时随机访问.您可以随时分配一个块并随时释放它.这使得在任何给定时间跟踪堆的哪些部分被分配或释放变得更加复杂.

对于转义关闭: - 要记住的一个重要注意事项是,如果在闭包中捕获存储在堆栈上的值,该值将被移动到堆,以便在执行闭包时它仍然可用.

  • 需要记住的一个重要注意事项是,如果在闭包中捕获存储在堆栈上的值,则该值将被移动到堆中,以便在执行闭包时它仍然可用. (7认同)
  • 因为您的答案的很大一部分是从这里复制的http://net-informations.com/faq/net/stack-heap.htm,所以最好将其链接作为参考。 (4认同)
  • @OleksandrKruk仅对转义闭包是正确的,因为只有那些可以在以后执行. (2认同)

Juu*_*uul 16

类(引用类型)在堆中分配,值类型(如Struct,String,Int,Bool等)存在于堆栈中.有关更详细的答案,请参阅此主题:为什么选择Struct Over Class?

  • 这不再是真的.当Swift可以证明值不会逃脱时,它可以优化一些分配以使它们成为堆栈分配.值与引用类型是概念上的差异,它不取决于值的分配位置. (6认同)
  • @russbishop感谢您的澄清。是否有Web链接,其中包含您提到的优化的更多说明? (2认同)
  • @russbishop 肯定会感谢带有解释的链接 (2认同)

yoA*_*ex5 10

堆栈与堆

Stack是线程的一部分。它由按 LIFO 顺序的方法(函数)框架组成。方法框架仅包含局部变量实际上,它是您在调试或分析错误[关于]期间看到的方法堆栈跟踪。

HeapARC [关于]发挥作用的记忆的另一部分。这里分配内存需要更多的时间(找到合适的地方并以同步方式分配)。

这些概念与[JVM图解]相同

Xcode 建议您使用下一个变体Debug Memory Graph

在此输入图像描述

*要查看回溯,请使用:

Edit Scheme... -> <Action> -> Diagnostics -> Malloc Stack Logging
Run Code Online (Sandbox Code Playgroud)

[值与引用类型]
[类与结构]


小智 7

通常,当我们问这样的问题(是堆栈还是堆)时,我们关心性能,并希望避免堆分配的过高成本。遵循“引用类型是堆分配的,值类型是堆栈分配的”一般规则可能会导致次优的设计决策,需要进一步讨论。

人们可能会错误地得出这样的结论:传递结构(值类型)通常比传递类(引用类型)更快,因为它不需要堆分配。事实证明这并不总是正确的。

重要的反例是协议类型,其中具有值语义(结构)的具体多态类型实现协议,就像这个玩具示例一样:

protocol Vehicle {
    var mileage: Double { get }
}

struct CombustionCar: Vehicle {
    let mpg: Double
    let isDiesel: Bool
    let isManual: Bool
    var fuelLevel: Double    // gallons
    var mileage: Double { fuelLevel * mpg }
}

struct ElectricCar: Vehicle {
    let mpge: Double
    var batteryLevel: Double // kWh
    var mileage: Double { batteryLevel * mpge / 33.7 }
}

func printMileage(vehicle: Vehicle) {
    print("\(vehicle.mileage)")
}

let datsun: Vehicle = CombustionCar(mpg: 18.19,
                                        isDiesel: false,
                                        isManual: false,
                                        fuelLevel: 12)
let tesla: Vehicle = ElectricCar(mpge: 132,
                                 batteryLevel: 50)
let vehicles: [Vehicle] = [datsun, tesla]
for vehicle in vehicles {
    printMileage(vehicle: vehicle)
}
Run Code Online (Sandbox Code Playgroud)

请注意,CombustionCarElectricCar对象具有不同的大小,但我们可以将它们混合在一系列Vehicle协议类型中。这就提出了一个问题:数组容器元素不需要具有相同的大小吗?如果编译器在编译时并不总是知道元素大小,那么它如何计算数组元素的偏移量?

事实证明,这背后隐藏着相当多的逻辑。Swift 编译器将创建所谓的Existential Container. 它是一个固定大小的数据结构,充当对象的包装器。这个容器被传递给函数调用(被压入堆栈)而不是实际的结构。

Existential Container长五个字:

|           |
|valueBuffer|
|           |
|    vwt    |
|    pwt    |
Run Code Online (Sandbox Code Playgroud)

前三个单词称为 the valueBuffer,这是存储实际结构的地方。好吧,除非结构大小大于三个字 - 在这种情况下,编译器将在堆上分配结构并将对其的引用存储在valueBuffer

    STACK                  STACK              HEAP

|   mpge     |         |  reference |-->|     mpg     |
|batteryLevel|         |            |   |   isDiesel  |
|            |         |            |   |   isManual  |
|    vwt     |         |     vwt    |   |   fuelLevel |
|    pwt     |         |     pwt    |
Run Code Online (Sandbox Code Playgroud)

因此,将这样的协议类型对象传递给函数实际上可能需要堆分配。编译器将进行分配并复制值,因此您仍然可以获得值语义,但成本会根据您的结构体长度是否为 3 个字或更长而有所不同。这使得“值类型在堆栈上,引用类型在堆上”并不总是正确的。