Understanding addresses of Swift array elements

Viewed 132

I've been looking at the addresses of Swift array elements and I'm surprised by the results. Here's my code for Bool elements:

let boolArray = [false, true, false, true]
withUnsafePointer(to: boolArray[0]) { print($0) }
withUnsafePointer(to: boolArray[1]) { print($0) }

Here are the printed results:

0x000000016fdff40e
0x000000016fdff40c

It looks like the first element is stored two bytes after the second. Bizarre.

Here's my code for Float elements:

let floatArray: [Float] = [1.1, 2.2, 3.3, 4.4]
withUnsafePointer(to: floatArray[0]) { print($0) }
withUnsafePointer(to: floatArray[1]) { print($0) }

Here are the printed results:

0x000000016fdff408
0x000000016fdff400

Now the first value is stored eight bytes after the second. Just to check, here's another way to access memory locations:

withUnsafePointer(to: numArray) {
    print($0)
    print($0.advanced(by: 1))
}

Here are the printed results:

0x000000016fdff418
0x000000016fdff420

This time, the first element is before the second, but they're separated by eight bytes. The array contains Floats, so I'd expected the second address to be four bytes after the first.

I'm sure I'm missing something obvious. Any ideas?

2 Answers

Case 1

You're printing the addresses of temporary values that you're getting as a result of subscripting the array. Try passing the value as a reference, instead:

var boolArray = [false, true, false, true]
withUnsafePointer(to: &boolArray[0]) { print($0) }
withUnsafePointer(to: &boolArray[1]) { print($0) }

var floatArray: [Float] = [1.1, 2.2, 3.3, 4.4]
withUnsafePointer(to: &floatArray[0]) { print($0) }
withUnsafePointer(to: &floatArray[1]) { print($0) }

Case 2

When you do this:

withUnsafePointer(to: numArray) {
    print($0)
    print($0.advanced(by: 1))
}

You're getting the address of the Array, not the buffer of the Array. The pointer type is UnsafePointer<Array<Float>>, not UnsafePointer<Float>, which is probably what you were expecting. You can confirm this with print(type(of: $0)).

When you advance this pointer by "1", you're advancing it by MemoryLayout<Array<Float>>.stride (which is 8 bytes on 64 bit Intel). That stride is the same regardless of the array's Element type (from what I can tell), but of course, don't rely on that detail. What you end up with is some piece of stack space just adjacent to your array. It does not look up the second float within the array.

To achieve what I think you were going for, you would use Array.withUnsafeBufferPointer(_:), or one of the other similar APIs of Array:

floatArray.withUnsafeBufferPointer {
    print(type(of: $0)) // UnsafeBufferPointer<Float>
    print($0.baseAddress!)
    print($0.baseAddress!.advanced(by: 1)) // 4 byte offset
}

Remember that Array is really just a struct which contains a pointer to a heap allocated buffer. All the elements are in that buffer, so if you're looking to poke around the memory, you need to look at the buffer, not the struct.

The problem is that floatArray[n] does not point into the array. To satisfy yourself, you could slice into the array:

let floatArray: [Float] = [1.1, 2.2, 3.3, 4.4]
floatArray.withUnsafeBufferPointer { ptr in
    print(ptr.baseAddress)
}
floatArray[1...].withUnsafeBufferPointer { ptr in
    print(ptr.baseAddress)
}

Once you are satisfied with that, you will see that you can walk the memory directly (i.e. it gives the same answer):

floatArray.withUnsafeBufferPointer { ptr in
    print(ptr.baseAddress)
    print(ptr.baseAddress?.advanced(by: 1))
}
Related