在swift中测试copy-on-write

时间:2017-06-25 14:42:17

标签: swift copy-on-write

 import Foundation

 func address(o:UnsafeRawPointer) -> Int {
     return Int(bitPattern: o)
 }
 var originArray = [1,2,3]
 var firstArray = originArray


//q.append(4)
print(NSString.init(format: "originArray:%p", address(o: &originArray)))
print(NSString.init(format: "firstArray:%p", address(o: &firstArray)))

调试日志: originArray:0x100b087b0 firstArray:0x100b088c0

上面,这是我的测试代码。我认为我不修改originArray,就像追加或减少元素一样。他们应该指出相同的地址。但为什么要尊重

2 个答案:

答案 0 :(得分:3)

您的代码在将值传递给指针参数时打印数组缓冲区的地址(Array is a special case。但是,在Swift 3中,编译器假定&运算符的存在意味着缓冲区作为可变内存传递,因此(不必要地)使其成为唯一(通过复制)之前传递其指针值,尽管指针值作为UnsafeRawPointer传递。这就是你看到不同地址的原因。

如果删除&运算符并直接传递数组:

func address(_ p: UnsafeRawPointer) {
    print(p)
}

var originArray = [1, 2, 3]
var firstArray = originArray

address(originArray) // 0x00000000016e71c0
address(firstArray)  // 0x00000000016e71c0

您现在将获得相同的地址,因为编译器现在假定address(_:)不会修改传递的缓冲区的内存,因为它们被传递给UnsafeRawPointer参数。

在Swift 4中,这种不一致性是固定的,并且编译器在将指针值传递给UnsafeRawPointer参数之前不再使缓冲区唯一,即使使用&运算符,所以代码展示预期的行为。

虽然,值得注意的是,当将相同的数组传递给多个指针参数时,上述方法不能保证产生稳定的指针值。

来自Swift博客文章“Interacting with C Pointers”:

  

即使您将相同的变量,数组或字符串作为多个指针参数传递,每次都可以接收不同的指针。

相信这种保证在两种情况下都不能满足(可能还有更多):

  1. 如果数组正在查看非连续存储中的元素

    Swift的Array可以查看非连续存储中的元素,例如当它包装NSArray时。在这种情况下,当将其传递给指针参数时,必须创建 new 连续缓冲区,从而为您提供不同的指针值。

  2. 如果缓冲区作为可变内存传递时是非唯一引用的

    如前所述,当将数组传递给可变指针参数时,其缓冲区将首先变为唯一,以保留值语义,因为假设该函数将执行缓冲区的变异。

    因此,如果复制了缓冲区,如果已将数组传递给不可变指针参数,则会获得不同的指针值。

  3. 虽然这两个点都不适用于你给出的例子,但值得记住的是,当传递给指针参数时,编译器仍然保证指向数组缓冲区的稳定指针值

    对于保证可靠的结果,您应该在withUnsafeBytes(_:)上使用ContiguousArray方法:

    var originArray: ContiguousArray = [1, 2, 3]
    var firstArray = originArray
    
    originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000102829550
    firstArray.withUnsafeBytes { print($0.baseAddress!) }  // 0x0000000102829550
    

    这是因为withUnsafeBytes(_:)被记录为接受:

      

    带有UnsafeRawBufferPointer参数的闭包,指向数组的连续存储。如果不存在此类存储,则会创建它。

    ContiguousArray保证:

      

    [it]始终将其元素存储在连续的内存区域

    就像Array一样,ContiguousArray使用copy-on-write以获得值语义,因此您仍然可以使用它来检查何时在发生突变时复制数组的缓冲区:

    var originArray: ContiguousArray = [1, 2, 3]
    var firstArray = originArray
    
    originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000103103eb0
    firstArray.withUnsafeBytes { print($0.baseAddress!) }  // 0x0000000103103eb0
    
    firstArray[0] = 4
    
    originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000103103eb0
    firstArray.withUnsafeBytes { print($0.baseAddress!) }  // 0x0000000100e764d0
    

答案 1 :(得分:1)

您正在打印变量本身的地址,而不是它所指向的数组缓冲区的地址。

您可以像这样获取数组缓冲区的地址:

var originArray = [1, 2, 3]
var firstArray = originArray

print("originArray: \(originArray.withUnsafeBytes { $0.baseAddress! })")
print("firstArray:  \(firstArray.withUnsafeBytes { $0.baseAddress! })")

现在打印相同的值,除非您修改其中一个数组。