UnsafeMutableRawPointer, binding and alignment

I've read @Andrew_Trick's excellent explanation about binding etc here but I'm still not entirely sure about the following.


Are there any situations where, assuming you've allocated memory to an UnsafeRawMutablePointer, you don't have to bind it to some type?

AFAICS, one such case would be if the data of that pointer would only ever be accessed via load(fromByteOffset: as:) and storeBytes(of: toByteOffset: as:). Correct?

I guess it depends on what exactly the following means (from the storeBytes documentation):

If the memory is bound to a type U that is layout compatible with T , then it contains a value of type U .

(There's no explicit mention of whether the memory must be bound to some type or if it's OK for it to not be bound to any type.)


And, a question about the following from the documentation for store and load:

The memory at this pointer plus offset must be properly aligned for accessing T

What exactly does this mean? I guess it just means that offset should point at the first byte of the value. Or does it mean that the address must be a multiple of MemoryLayout<T>.alignment, meaning the following example is incorrect or unsafe?

func example() {
  let ptr = UnsafeMutableRawPointer.allocate(byteCount: 9, alignment: 16)

  ptr.storeBytes(of: UInt8(123), toByteOffset: 0, as: UInt8.self)
  ptr.storeBytes(of: Double(4.56), toByteOffset: 1, as: Double.self)

  let a = ptr.load(fromByteOffset: 0, as: UInt8.self)
  let b = ptr.load(fromByteOffset: 1, as: Double.self)
  print(a) // 123
  print(b) // 4.56

  ptr.deallocate()
}
example()

In this example there is no binding, and the Double value is stored at an address which is not a multiple of MemoryLayout<Double>.alignment, and I assume that that's OK/correct/safe.

Please correct me if I'm wrong.

1 Like

Correct, you are never required to bind a pointer. If you want to access it entirely through the raw pointer, that is fine. You can also perform bytewise memory copies to and from that pointer while it remains raw.

It is not, and your specific example will likely crash, at least in debug mode, due to assertions within UnsafeMutableRawPointer.store.

You are right that what store’s documentation is discussing is about the alignment of the memory to the value stored within it. Specifically, the pointer must match the alignment requirement of the types. Double is an 8-byte value and so has an 8-byte alignment requirement. If you store to a non-8-byte-aligned address, you are breaking the API contract of load/store.

I have been annoyed by this for a while because I think the benefits are minimal but the costs in confusion are high. You can work around this by using memcpy and friends to copy the bytes bytewise into the raw pointer.

1 Like

Ah, I should of course have compiled that in debug mode before asking ...

$ swiftc test.swift
$ ./test
Fatal error: storeBytes to misaligned raw pointer: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/UnsafeRawPointer.swift, line 947
Illegal instruction: 4

Yes, my use case is a tightly packed byte buffer (for serialization, essentially something like a very basic binary Encoder / Decoder), so I guess I'll have to reformulate it (and remember to not accidentally get stuck in compiling in release mode :slight_smile: ).

I recommend treating NIO as an example here: we support writing packed structures, and so the things we do with pointers are likely worth following. In this case, memcpy is your friend.

2 Likes