2

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

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

但是,这里讨论的冲突访问可能发生在单个线程上,不涉及并发或多线程代码。

详细说明:这里有两个来自The Swift Programming Language (Swift 4.1 beta) 的例子。我很困惑+=结构中的这种自定义实现如何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”的即时读取访问”与“步”的长期书写重叠。或者,当您为两个 inout 参数传入同一个实例时,这只是一个问题,而不是一个 inout 和一个常规参数?

4

2 回答 2

2

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

...最终,任何单行代码通过首先读取 self 来更改 self 都是一个问题。

不,仅此还不够。正如内存安全一章所说,这个问题只有在以下情况下才会出现:

  • 至少一个是写访问。
  • 它们访问内存中的相同位置。
  • 它们的持续时间重叠。

考虑:

var foo = 41
foo = foo + 1

foo = foo + 1不是问题(也不会foo += 1;也不会foo += foo),因为它构成了一系列“瞬时”访问。所以虽然我们有(用你的话)“通过首先阅读自我来改变自我”,但这不是问题,因为它们的持续时间不重叠。

只有当您处理“长期”访问时,问题才会显现出来。正如该指南继续说的:

函数对其所有输入输出参数具有长期写入权限。对 in-out 参数的写访问在所有非 in-out 参数被评估后开始,并持续到该函数调用的整个持续时间。如果有多个 in-out 参数,则写入访问的开始顺序与参数出现的顺序相同。

这种长期写访问的一个后果是您无法访问作为输入输出传递的原始变量,即使范围规则和访问控制允许它 - 对原始变量的任何访问都会产生冲突。

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

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

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

于 2018-04-14T18:32:13.690 回答
1

当你写

x +=5

等于

x = x + 5

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

于 2018-04-14T15:03:58.603 回答