This is how you can drill into swift's data structures. Use at your own risk:
import Foundation
func addressToBytes(_ address: UnsafeRawPointer) -> UnsafePointer<UInt8>? {
address.assumingMemoryBound(to: UInt8.self)
}
func printBytes(_ address: UnsafeRawPointer, offset: Int = 0, count: Int) {
let p = addressToBytes(address + offset)!
print(String(format: "%016lx: ", p), terminator: "")
for i in 0 ..< count {
let s = String(format: "%02x ", p[i])
print(s, terminator: "")
}
print()
}
func getPointer(_ address: UnsafeRawPointer, offset: Int) -> UnsafeRawPointer? {
let p = (address + offset).assumingMemoryBound(to: UnsafeRawPointer.self)
return p.pointee
}
struct NumArr<R> {
var vals: [R]
var magic: UInt64 = 0x1122334455667788
}
var a = NumArr<UInt8>(vals: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25])
var b = a
let size = MemoryLayout.size(ofValue: a)
print("bytes of a:")
printBytes(&a, count: size)
print("bytes of b:")
printBytes(&b, count: size)
a.vals[0] = 0x7F
print("new bytes of a:")
printBytes(&a, count: size)
print("new bytes of b:")
printBytes(&b, count: size)
// let's drill into it further:
var av = getPointer(&a, offset: 0)!
var bv = getPointer(&b, offset: 0)!
var avSize = malloc_size(av)
print("contents of a's array: (\(avSize == 0 ? "first bytes" : "known to be malloced block of size \(avSize)"))")
printBytes(av, count: avSize == 0 ? 64 : avSize)
var bvSize = malloc_size(bv)
print("contents of b's array: (\(bvSize == 0 ? "first bytes" : "known to be malloced block of size \(bvSize)"))")
printBytes(bv, count: bvSize == 0 ? 64 : bvSize)
Note that I've changed your struct a little bit to easier see what's going on without scrolling too much:
struct NumArr<R> {
var vals: [R]
var magic: UInt64 = 0x1122334455667788
}
At start:
bytes of a:
0000000100008048: 00 02 70 01 00 60 00 00 88 77 66 55 44 33 22 11
bytes of b:
0000000100008058: 00 02 70 01 00 60 00 00 88 77 66 55 44 33 22 11
So far so good, as you can see both contents are equal at this point, both point to the same array. If you wonder why the bytes are in the wrong order - we are on a little endian computer.
Now changing one of the arrays:
a.vals[0] = 0x7F
new bytes of a:
0000000100008048: 00 03 70 01 00 60 00 00 88 77 66 55 44 33 22 11
new bytes of b:
0000000100008058: 00 02 70 01 00 60 00 00 88 77 66 55 44 33 22 11
As you can see a
's array is now different - COW machinery did its job.
To further see the internals:
var av = getPointer(&a, offset: 0)!
var bv = getPointer(&b, offset: 0)!
Obviously if we got the memory layout wrong that would either crash or give us some garbage, but it appears we know what we are doing:
contents of a's array: (known to be malloced block of size 64)
0000600001700300: 48 59 9d db 01 00 00 00 03 00 00 00 00 00 00 00 19 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 7f 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 0a 1a dc 01 00 00 00
contents of b's array: (known to be malloced block of size 64)
0000600001700200: 48 59 9d db 01 00 00 00 03 00 00 00 00 00 00 00 19 00 00 00 00 00 00 00 32 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 4e 8f db 01 00 00 00
I highlighted array contents in bold. The rest of the array contains things like size (25 == 0x19), capacity and some internal stuff.
Now you have the superpowers – use it wisely! 