从内存安全:
具体来说,如果您有两个满足以下所有条件的访问权限,则会发生冲突:
- 至少一个是写访问或非原子访问。
- 它们访问内存中的相同位置。
- 它们的持续时间重叠。
然后:
函数对其所有输入输出参数具有长期写入权限。对 in-out 参数的写访问在所有非 in-out 参数被评估后开始,并持续到该函数调用的整个持续时间。
因此,将与 in-out 参数相同的变量传递给
func balance(_ x: inout Int, _ y: inout Int)
算作对同一内存位置的重叠写访问,因此算作冲突。
有关基本原理和更多详细信息,请参阅SE-0176 Enforce Exclusive Access to Memory,它已在 Swift 4 中实现。特别是,强制独占内存访问
- 防止未定义的行为和意外或令人困惑的结果。
- 允许编译器对加载和存储做出乐观的假设。
例如,如果将相同的变量作为 inout 参数传递,则以下两个看似等效的函数实际上是不等效的,因此变异x
可以影响y
,反之亦然:
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
func balance(_ x: inout Int, _ y: inout Int) {
x = (x + y) / 2
y = (x + y) / 2 - x
}
另一个例子:
var global = 0
func foo(_ x: inout Int, _ y: inout Int) {
x += y
global = y
}
如果变异x
可能会修改y
,反之亦然,编译器无法优化代码以首先将值加载到y
寄存器中,即执行相当于
func foo(_ x: inout Int, _ y: inout Int) {
let savedY = y
x += savedY
global = savedY
}
还讨论了(参见消除非瞬时访问?)通过在函数调用期间制作临时副本来消除长期访问,这些临时副本在函数返回时分配回,即执行类似的操作
func balance(_ x: inout Int, _ y: inout Int) {
var (localX, localY) = (x, y)
let sum = localX + localY
localX = sum / 2
localY = sum - localX
(x, y) = (localX, localY)
}
这个想法被抛弃了,因为它不利于性能,即使对于“简单类型”也是如此,但对于像Array
.