How can you create an “aligned” array of bytes and read from it?

I want to be able to use the UnsafeRawBufferPointer.load(fromByteOffset:as:) method to read a number of bytes from a [UInt8] array and their corresponding type as an UnsignedInteger, FixedWidthInteger at each read.

In both approaches that follow, a "Fatal error: load from misaligned raw pointer" exception is raised, since load expects the underlying data to be aligned in memory.

I have tried using a ContiguousArray

Code Block
var words: ContiguousArray<UInt8> = [0x01, 0x00, 0x03, 0x0a, 0x00, 0x01, 0x00, 0xec, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x20, 0x00, 0x00, 0xe0, 0x88, 0x47, 0xa3, 0xd6, 0x6b, 0xd6, 0x01, 0x4c, 0xff, 0x08]
var offset = 0
let byte = words.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
offset += MemoryLayout<UInt8>.size
let bytes = words.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) }
XCTAssertEqual(byte, UInt8(littleEndian: 0x01))
XCTAssertEqual(bytes, UInt16(littleEndian: 0x0003))


Allocating and initialising a UnsafeMutablePointer

Code Block
var words: [UInt8] = [0x01, 0x00, 0x03, 0x0a, 0x00, 0x01, 0x00, 0xec, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x20, 0x00, 0x00, 0xe0, 0x88, 0x47, 0xa3, 0xd6, 0x6b, 0xd6, 0x01, 0x4c, 0xff, 0x08]
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: words.count)
uint8Pointer.initialize(from: &words, count: words.count)
let rawPointer = UnsafeMutableRawPointer(uint8Pointer)
var offset = 0
let byte = UInt8(bigEndian: rawPointer.load(fromByteOffset: offset, as: UInt8.self))
offset += MemoryLayout<UInt8>.size
let bytes = UInt16(bigEndian: rawPointer.load(fromByteOffset: offset, as: UInt16.self))
rawPointer.deallocate()
uint8Pointer.deinitialize(count: words.count)
XCTAssertEqual(byte, UInt8(littleEndian: 0x01))
XCTAssertEqual(bytes, UInt16(littleEndian: 0x0003))


Can you please point out where my misunderstanding lies in each and provide a working example?

Replies


I want to be able to use the UnsafeRawBufferPointer.load(fromByteOffset:as:) method to read a number of bytes from a [UInt8] array 

You know two facts:
  • UnsafeRawBufferPointer.load(fromByteOffset:as:) requires the raw pointer to be aligned

  • Your offset may generate a raw pointer which is NOT aligned

Conclusion:

You cannot use UnsafeRawBufferPointer.load(fromByteOffset:as:) for your purpose.

QED


You can write something like this:
Code Block
extension Array where Element == UInt8 {
func readLittleEndian<T: FixedWidthInteger>(offset: Int, as: T.Type) -> T {
assert(offset + MemoryLayout<T>.size <= self.count)
//Prepare a region aligned for `T`
var value: T = 0
//Copy the misaligned bytes at `offset` to aligned region `value`
_ = Swift.withUnsafeMutableBytes(of: &value) {valueBP in
self.withUnsafeBytes {bufPtr in
let range = offset..<offset+MemoryLayout<T>.size
bufPtr.copyBytes(to: valueBP, from: range)
}
}
return T(littleEndian: value)
}
}

(You can easily write readBigEndian.)

And use it as follows:
Code Block
//You have no need to use `ContiguousArray`
var words: [UInt8] = [0x01, 0x00, 0x03, 0x0a, 0x00, 0x01, 0x00, 0xec, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x20, 0x00, 0x00, 0xe0, 0x88, 0x47, 0xa3, 0xd6, 0x6b, 0xd6, 0x01, 0x4c, 0xff, 0x08]
var offset = 0
let byte = words.readLittleEndian(offset: offset, as: UInt8.self)
print(String(format: "%02X", byte)) //->01
offset += MemoryLayout<UInt8>.size
let bytes = words.readLittleEndian(offset: offset, as: UInt16.self)
print(String(format: "%04X", bytes)) //->0300


Please try.

Please bare with me as I improve my understanding.

What's the use case for UnsafeRawBufferPointer.load(fromByteOffset:as:) when used with an offset?
Can you please provide an example?

Are you suggesting there is no way to satisfy the precondition for UnsafeRawBufferPointer.load(fromByteOffset:as:), when using an offset, in Swift?

when using an offset, in Swift?

When using an unaligned offset, true. There is no way to satisfy.

What's the use case for UnsafeRawBufferPointer.load(fromByteOffset:as:) when used with an offset?

In case offset is aligned to the target type.
So in other words, assuming I got this right, one way to guarantee an “aligned” array of bytes is by using the same UnsignedIntegerFixedWidthInteger type. Since the array is homogenous, alignment is guaranteed by the size of each type.

e.g. a [UInt8] array that holds byte types

Code Block
        var array: [UInt8] = [0x01, 0x00, 0x03]
        var offset = 0
        let byte = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
        offset += MemoryLayout<UInt8>.size
        let bytes = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
        offset += MemoryLayout<UInt8>.size
        let bytess = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
        XCTAssertEqual(byte, 0x01)
        XCTAssertEqual(bytes, 0x00)
        XCTAssertEqual(bytess, 0x03)


e.g. a [UInt8] array that holds 2 byte types
Code Block
        var array: [UInt8] = [0x01, 0x00, 0x03, 0x0a, 0x00, 0x01]
        var offset = 0
        let byte = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
        offset += MemoryLayout<UInt16>.size
        let bytes = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
        offset += MemoryLayout<UInt16>.size
        let bytess = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
        XCTAssertEqual(byte, 0x0100)
        XCTAssertEqual(bytes, 0x030a)
        XCTAssertEqual(bytess, 0x0001)


The misunderstanding lies in that, it is not possible to have an "aligned" array of bytes when types stored in the array can't be aligned given their mismatched size.

The size of an UInt8 being 1 byte and the size of UInt16 being 2 bytes in this case creating an unaligned array that is not supported by  UnsafeRawBufferPointer.load(fromByteOffset:as:).


The size of an UInt8 being 1 byte and the size of UInt16 being 2 bytes in this case creating an unaligned array that is not supported by  UnsafeRawBufferPointer.load(fromByteOffset:as:).

Quite right.
Thank you for guiding me through this!