26.内存安全

2021-07-28  本文已影响0人  LucXion

内存访问的三个性质:读还是写,访问的内存地址,访问的时长

只要包含以下情况中的任意两种,都会造成 访问冲突

In-Out 参数的访问冲突

一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。

因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 changeTwo(_:_:) 是一个名为 <^> 的操作符函数,那么 num1 <^> num1 也会造成像 changeTwo(&num1, &num1)一样的冲突。

var num1 = 1
var num2 = 2
func changeNum(with num:inout Int)->Int {
    return num + num1
}
print(changeNum(with:  &num1)) // 错误:changeNum 内部直接访问了 num1

func changeTwo(num1:inout Int,num2:inout Int)->Int{
    return num1 + num2
}
changeTwo(num1: &num1, num2: &num1) // 错误:包含写访问,访问同一个内存地址,访问时间线重叠

方法里 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)
    }
}
func balance(_ fromHealth:inout Int,_ toHealth:inout Int) {
    fromHealth = 1
    toHealth = 2
    print(fromHealth,toHealth)
}

var player = Player.init(name: "Li", health: 11, energy: 8)
//player.shareHealth(with: &player)// 错误:shareHealth内隐式访问self,会造成访问同一个内存地址
var player2 = Player.init(name: "Ming", health: 15, energy: 8)
player.shareHealth(with: &player2) // 正确 

属性的访问冲突

var player = Player.init(name: "Li", health: 11, energy: 8)
balance(&player.health, &player.energy) // 运行错误: 值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值

func someFunction (){
    var player2 = Player.init(name: "Ming", health: 15, energy: 8)
    balance(&player2.health, &player2.energy) // 运行正常:改为本地变量而非全局变量,编译器就会可以保证这个重叠访问是安全的

限制结构体属性的重叠访问对于保证内存安全不是必要的。保证内存安全是必要的,但因为访问独占权的要求比内存安全还要更严格——意味着即使有些代码违反了访问独占权的原则,也是内存安全的,所以如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种行为的代码运行。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的:

如果编译器无法保证访问的安全性,它就不会允许那次访问。

上一篇下一篇

猜你喜欢

热点阅读