0

I am building a Data object that looks something like this:

struct StructuredData {
  var crc: UInt16
  var someData: UInt32
  var someMoreData: UInt64
  // etc.
}

I'm running a CRC algorithm that will start at byte 2 and process length 12.

When the CRC is returned, it must exist at the beginning of the Data object. As I see it, my options are:

  1. Generate a Data object that does not include the CRC, process it, and then build another Data object that does (so that the CRC value that I now have will be at the start of the Data object.

  2. Generate the data object to include a zero'd out CRC to start with and then mutate the data at range [0..<2].

Obviously, 2 would be preferable, as it uses less memory and less processing, but this type of an optimization I'm not sure is necessary anymore. I'd still rather go with 2, except I do not know how to mutate the data at a given index range. Any help is greatly appreciated.

Brandon M
  • 349
  • 2
  • 20

2 Answers2

2

I do not recommend the way of mutating Data using this:

data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))

Please try this:

data.replaceSubrange(0..<2, with: &self.crc, count: 2)

It is hard to explain why, but I'll try...

In Swift, inout parameters work in copy-in-copy-out semantics. When you write something like this:

aMethod(&param)
  • Swift allocates some region of enough size to hold the content of param,

  • copies param into the region, (copy-in)

  • calls the method with passing the address of the region,

  • and when returned from the call, copies the content of the region back to param (copy-out).

In many cases, Swift optimizes (which may happen even in -Onone setting) the steps by just passing the actual address of param, but it is not clearly documented.

So, when an inout parameter is passed to the initializer of UnsafeBufferPointer, the address received by UnsafeBufferPointer may be pointing to a temporal region, which will be released immediately after the initializer is finished.

Thus, replaceSubrange(_:with:) may copy the bytes in the already released region into the Data.

I believe the first code would work in this case as crc is a property of a struct, but if there is a simple and safe alternative, you should better avoid the unsafe way.


ADDITION for the comment of Brandon Mantzey's own answer.

data.append(UnsafeBufferPointer(start: &self.crcOfRecordData, count: 1))

Using safe in the meaning above. This is not safe, for the same reason described above.

I would write it as:

data.append(Data(bytes: &self.crcOfRecordData, count: MemoryLayout<UInt16>.size))

(Assuming the type of crcOfRecordData as UInt16.)

If you do not prefer creating an extra Data instance, you can write it as:

withUnsafeBytes(of: &self.crcOfRecordData) {urbp in
     data.append(urbp.baseAddress!.assumingMemoryBound(to: UInt8.self), count: MemoryLayout<UInt16>.size)
}

This is not referred in the comment, but in the meaning of above safe, the following line is not safe.

let uint32Data = Data(buffer: UnsafeBufferPointer(start: &self.someData, count: 1))

All the same reason.

I would write it as:

let uint32Data = Data(bytes: &self.someData, count: MemoryLayout<UInt32>.size)

Though, observable unexpected behavior may happen in some very limited condition and with very little probability.

Such behavior would happen only when the following two conditions are met:

  1. Swift compiler generates non-Optimized copy-in-copy-out code

  2. Between the very narrow period, since the temporal region is released till the append method (or Data.init) finishes copying the whole content, the region is modified for another use.

The condition #1 becomes true only limited cases in the current implementation of Swift.

The condition #2 happens very rarely only in the multi-threaded environment. (Though, Apple's framework uses many hidden threads as you can find in the debugger of Xcode.)

In fact, I have not seen any questions regarding the unsafe cases above, my safe may be sort of overkill.

But alternative safe codes are not so complex, are they? In my opinion, you should better be accustomed to use all-cases-safe code.

OOPer
  • 47,149
  • 6
  • 107
  • 142
0

I figured it out. I actually had a syntax error that was boggling me because I hadn't seen that before.

Here's the answer:

data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))
Brandon M
  • 349
  • 2
  • 20
  • Unfortunately, your code is not _safe_... Please check my answer. – OOPer Sep 25 '18 at 19:25
  • Thank you. I don't want to get too off topic, but it seems a bit much to start a new question. By what you're saying in your answer, would you say that `data.append(UnsafeBufferPointer(start: &self.crcOfRecordData, count: 1))` is safe? – Brandon M Sep 25 '18 at 21:46
  • If not, what alternative would you recommend? If you think I should just start a new question, please let me know. Thanks again. – Brandon M Sep 25 '18 at 21:46
  • Just a moment, I will update my answer, as for now, I cannot have enough time. – OOPer Sep 26 '18 at 00:20
  • 1
    Done. Please check the added part of my answer. – OOPer Sep 26 '18 at 02:22