I have a C struct that stores a MAC address as an array of 6 unsigned characters (u_char) i.e.
u_char mac[6];
In swift this array is seen as a tuple (UInt8,UInt8,UInt8,UInt8,UInt8,UInt8)
I want to store this tuple into a Swift array in the fastest way possible. Currently I am using memcpy but wondered if there is a more Swiftified way of doing this without sacrificing speed.
var dst_mac: [u_char] {
var a = [u_char](repeating: 0, count: 6)
var mac = m_flow.dst_mac
memcpy(&a, &mac, 6)
return a
}
I’m not happy with the code I posted earlier. The fundamental problem is this snippet:
UnsafeBufferPointer(start: &tmp.0, …)
There’s actually a couple of issues:
-
The
tmp.0
pointer generated by the&
is only valid for the duration of theUnsafeBufferPointer.init(start:count:)
initialiser, which means it could be invalid at the point that the[UInt8].init(_:)
runs. In practice this doesn’t happen, but it could. -
Using
tmp.0
only pins the first element of the tuple, not the whole tuple.
For more background on this overall issue, see The Peril of the Ampersand.
A better option is this:
struct Foo {
var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
var array: [UInt8] {
withUnsafeBytes(of: self.tuple) { buf in
[UInt8](buf)
}
}
}
This bounces through a UnsafeRawBufferPointer
which is convenient and safe in this case, where the tuple elements are just bytes. This is also the most common case, because you usually see this issue when dealing with fixed-sized arrays of char
imported from C.
If you’re dealing with some other type you need more complex code. For example:
struct Foo {
var tuple: (CInt, CInt, CInt, CInt, CInt, CInt)
var array: [CInt] {
return withUnsafePointer(to: self.tuple) { tuplePtr in
let start = tuplePtr.qpointer(to: \.0)!
let count = MemoryLayout.size(ofValue: tuplePtr.pointee) / MemoryLayout.size(ofValue: tuplePtr.pointee.0)
let buf = UnsafeBufferPointer(start: start, count: count)
return [CInt](buf)
}
}
}
Here the withUnsafePointer(…)
call pins the entire tuple at a fixed location in memory, which allows you to safely get a pointer to one of its elements.
Note that the qpointer(…)
method is my version of the helper method that we’re adding as part of SE-0334 Pointer API Usability Improvements. Here’s an implementation you can use today:
extension UnsafePointer {
public func qpointer<Property>(to property: KeyPath<Pointee, Property>) -> UnsafePointer<Property>? {
guard let offset = MemoryLayout<Pointee>.offset(of: property) else { return nil }
return (UnsafeRawPointer(self) + offset).assumingMemoryBound(to: Property.self)
}
}
The take-home lesson here: Pointer manipulation is tricky, even in Swift.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"