Buer
Buer

Reputation: 122

Test Copy-on-write in swift

 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)))

Debug Log: originArray:0x100b087b0 firstArray:0x100b088c0

Above, it's my test code.I think that I don't modify originArray which like appending or reduceing element. they should point same address.but why deference

Upvotes: 3

Views: 578

Answers (2)

Hamish
Hamish

Reputation: 80781

Your code is printing the addresses of the array buffers (Array is a special case when passing a value to a pointer parameter). However, in Swift 3, the compiler assumed that the presence of the & operator meant that the buffer was being passed as mutable memory, so (unnecessarily) made it unique (by copying) before passing its pointer value, despite that pointer value being passed as an UnsafeRawPointer. That's why you see different addresses.

If you remove the & operator and pass the arrays directly:

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

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

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

You'll now get the same addresses, as the compiler now assumes that address(_:) will not modify the memory of the buffers passed, as they're being passed to an UnsafeRawPointer parameter.

In Swift 4, this inconsistency is fixed, and the compiler no longer makes the buffer unique before passing its pointer values to an UnsafeRawPointer parameter, even when using the & operator, so your code exhibits expected behaviour.

Although, it's worth noting that the above method isn't guaranteed to produce stable pointer values when passing the same array to multiple pointer parameters.

From the Swift blog post "Interacting with C Pointers":

Even if you pass the same variable, array, or string as multiple pointer arguments, you could receive a different pointer each time.

I believe this guarantee cannot be met for arrays in two cases (there may be more):

  1. If the array is viewing elements in non-contiguous storage

    Swift's Array can view elements in non-contiguous storage, for example when it is wrapping an NSArray. In such a case, when passing it to a pointer parameter, a new contiguous buffer will have to be created, therefore giving you a different pointer value.

  2. If the buffer is non-uniquely referenced when passed as mutable memory

    As mentioned earlier, when passing an array to a mutable pointer parameter, its buffer will first be made unique in order to preserve value semantics, as it's assumed the function will perform a mutation of the buffer.

    Therefore, if the buffer was copied, you'll get a different pointer value to if you had passed the array to an immutable pointer parameter.

Although neither of these two points are applicable in the example you give, it's worth bearing in mind that the compiler still doesn't guarantee you stable pointer values to the array's buffer when passing to pointer parameters.

For results that are guaranteed to be reliable, you should use the withUnsafeBytes(_:) method on a ContiguousArray:

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

originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000102829550
firstArray.withUnsafeBytes { print($0.baseAddress!) }  // 0x0000000102829550

This is because withUnsafeBytes(_:) is documented as accepting:

A closure with an UnsafeRawBufferPointer parameter that points to the contiguous storage for the array. If no such storage exists, it is created.

And ContiguousArray guarantees that:

[it] always stores its elements in a contiguous region of memory

And just like Array, ContiguousArray uses copy-on-write in order to have value semantics, so you can still use it to check when the array's buffer is copied upon a mutation taking place:

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

Upvotes: 5

Emil Laine
Emil Laine

Reputation: 42828

You're printing the address of the variable itself, not the address of the array buffer it's pointing to.

You can get the address of the arrays' buffers like so:

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

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

Now the same value is printed, unless you modify one of the arrays.

Upvotes: 1

Related Questions