内存安全(Memory Safety)(学习笔记)
环境Xcode 11.0 beta4 swift 5.1
欢迎留言 pen me
-
前言
- 默认情况下,Swift会阻止在你代码中的不安全行为,例如:变量没初始化前不可使用、被销毁的内存不可访问、数组索引越界检查会报错;Swift也能确保在代码修改内存中值时将会独占该,从而确保多处同时访问相同内存时不会冲突。因为Swift自动管理内存,大部时候不需要去关心内存的访问;但了解潜在的内存冲突很重要,这样可以在写代码避免;如果代码包含了冲突,将可能在编译或运行时出现错误。
-
理解内存访问冲突
-
下面是内存的读和写
// write memory var one = 1 // read memory print("We'er number \(one)!")
- 内存冲突可能发生在不同部分代码访问相同内存时发生,下图中的 Before During After 来说明这个问题
- Before 和 After 状态访问时将获得正确的答案
- 在 During 状态时是一个瞬时的状态,当添加物品时,总价可能还没来得及更改,此时访问可能得到不正确的答案
有时候修复冲突的方法有多种,而且在修复之前要搞清楚你的需求是什么,例如在本例中,是要访问5还是300
-
- 存储器访问特性,主要有3个特性:一、访问是读还是写; 二、访问的持续时间; 三、被访问的内存位置;如果有两个访问满足以下条件将会发生冲突
- 至少有一个写访问
- 访问同一块内存
- 访问时间有重叠
-
读访问和写访问还是有区别的,读要改变内存而写不需要;内存访问可以是瞬时的也可是持续的,如果其它代码无法在访问开始之后且在访问结束之前运行,则访问是瞬时的
从本质上讲,瞬时访问不可能同时发生的,大多数访问是即时的。示例如下:func oneMore(than number: Int) -> Int { return number + 1 } // var myNumber = 1 myNumber = oneMore(than: myNumber) print(myNumber) // Prints "2"
有多种方法访问内存,跨度超过其它代码的执行,这叫持续访问;一个持续访问可以与其它持续访问或瞬时访问发生重叠;这种重叠主要发生在
in-out
参数 和 结构体的mutating
方法-
in-out
参数访问冲突-
示例如下
var stepSize = 1; func increment(_ number: inout Int){ number += stepSize } increment(&stepSize) // Error: conflicting access to stepSize // 此处读访问与写时有重叠,如下图
// 解决冲突的方法 // Make an explicit copy. var copyOfStepSize = stepSize increment(©OfStepSize) // Update the original. stepSize = copyOfStepSize // stepSize is now 2
-
将同一个变量作为同一个函数的多个 in-out 参数传递会发生冲突
swift func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum / 2 y = sum - x } var playerOneScore = 42 var playerTwoScore = 30 balance(&playerOneScore, &playerTwoScore) // OK balance(&playerOneScore, &playerOneScore) // Error: conflicting accesses to playerOneScore
-
方法内访问
self
冲突-
示例
struct Player { var name: String var health: Int var energy: Int static let maxHealth = 10 mutating func restoreHealth() { health = Player.maxHealth } } extension Player { mutating func shareHealth(with teammate: inout Player) { balance(&teammate.health, &health) } } // var oscar = Player(name: "Oscar", health: 10, energy: 10) var maria = Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // OK 访问的内存不同,如下图 oscar.shareHealth(with: &oscar) // Error: conflicting accesses to oscar 如下下图
-
-
访问属性时冲突
- 像结构体、元组、枚举是独立的值,因为它们是值类型,读或者写都是整一块内存的访问,如下访问元组的元素
var playerInformation = (health: 10, energy: 20) balance(&playerInformation.health, &playerInformation.energy) // Error: conflicting access to properties of playerInformation
- 访问结构体属性
var holly = Player(name: "Holly", health: 10, energy: 10) balance(&holly.health, &holly.energy) // Error // 但下面的情况是可以的,如果上面的变量 holly 是局部变量而不是全局变量,编译器可以证明这种重复访问是安全的,因为两个存储属性的内存不可能有交叉 func someFunction() { var oscar = Player(name: "Oscar", health: 10, energy: 10) balance(&oscar.health, &oscar.energy) // OK }
- 当满足以下几点时,SWift编译器可以保证重复访问结构体属性时是安全的
- 只访问存储属性,不访问计算属性和类型属性
- 结构体变量是一个局部变量,非全局变量
- 结构体既没有被闭包捕获也没有非逃逸闭包捕获