Swift中“+=”运算符的内存安全

Memory safety of “+=” operator in Swift

我一直在学习swift,遇到一个关于内存安全的问题。 += 运算符在左侧接受一个 inout 参数,它应该对整个函数调用具有写访问权限。它在其实现中做了类似 left = right+left 的事情。这似乎是写入和读取访问的重叠。这怎么不违反内存安全?

编辑: 根据 The Swift Programming Language,它可以在单个线程中发生:

However, the conflicting access discussed here can happen on a single thread and doesn’t involve concurrent or multi-threaded code.

详述: 以下是 Swift 编程语言(Swift 4.1 测试版)中的两个示例。 我很困惑这个自定义 += 在结构 Vector2D 没问题:

static func += (left: inout Vector2D, right: Vector2D) {
    left = left + right
}

当这不是:

var stepSize = 1
func incrementInPlace(_ number: inout Int) {
    number += stepSize
}
incrementInPlace(&stepSize)
// Error: conflicting accesses to stepSize

进一步编辑:

我认为我的问题确实是 += 作为一个函数,特别是在使用时

stepSize += stepSize

或自定义实现:

var vector = Vector2D(x: 3.0, y: 1.0)
vector += vector

这没有任何错误。但是 func 从左边获取一个 inout,因此对“step”有一个长期的写访问权限,然后如果右边也传入了“step”,我很困惑这怎么不是对“step”的即时读取访问”与“step”的长期写作重叠。还是仅当您为两个 inout 参数传递同一实例而不是一个 inout 和一个常规参数时才会出现问题?

写的时候

x +=5

等于

x = x + 5

首先对变量 x 进行读取操作,然后将值添加到 5,最后对结果进行写入操作,所有这些都是通过寄存器同步发生的,而不是同时发生

我知道你已经明白了,但要为未来的读者澄清一下;在您的评论中,您说:

... it is, in the end, a problem with any one-line code changing self by reading self first.

不,仅此还不够。正如 Memory Safety 章节所说,只有在以下情况下才会出现此问题:

  • At least one is a write access.
  • They access the same location in memory.
  • Their durations overlap.

考虑:

var foo = 41
foo = foo + 1

foo = foo + 1 不是问题(foo += 1 也不会;foo += foo 也不会),因为它构成了一系列 "instantaneous" 访问。因此,尽管我们有(使用您的短语)"code changing self by reading self first",但这不是问题,因为它们的持续时间不重叠。

只有当您处理 "long-term" 访问时,问题才会显现出来。 guide 继续说:

A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.

One consequence of this long-term write access is that you can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict.

所以,考虑你的第二个例子:

var stepSize = 1
func incrementInPlace(_ number: inout Int) {
    number += stepSize
}
incrementInPlace(&stepSize)

在这种情况下,您可以长期访问任何 number 引用。当您使用 &stepSize 调用它时,这意味着您可以长期访问与 stepSize 关联的内存,因此 number += stepSize 意味着您正在尝试访问 stepSize虽然您已经可以长期访问它。