Без режима отладки UnsafePointer withMemoryRebound будет давать неправильное значение - PullRequest
0 голосов
/ 10 мая 2018

Здесь я пытаюсь объединить 5 байтов в одно целочисленное значение, у меня возникает проблема с методом UnsafePointer withMemoryRebound. когда я отлаживаю и проверяю логи, это дает правильное значение. Но когда я пытаюсь без отладки, это даст неправильное значение. (4 из 5 раз неправильное значение). Я запутался в этом API. Это правильный способ, которым я пользуюсь?

дело 1:

let data = [UInt8](rowData) // rowData is type of Data class
let totalKM_BitsArray = [data[8],data[7],data[6],data[5],data[4]]
self.totalKm = UnsafePointer(totalKM_BitsArray).withMemoryRebound(to:UInt64.self, capacity: 1) {$0.pointee}

дело 2:

Приведенный ниже код будет работать как для режима «Отключение», так и для режима «Отключить», и дает правильное значение

    let byte0 : UInt64 = UInt64(data[4])<<64
    let byte1 : UInt64 = UInt64(data[5])<<32
    let byte2 : UInt64 = UInt64(data[6])<<16
    let byte3 : UInt64 = UInt64(data[7])<<8
    let byte4 : UInt64 = UInt64(data[8])
    self.totalKm = byte0 | byte1 | byte2 | byte3 | byte4

Пожалуйста, предложите мне способ использования UnsafePointer? Почему эта проблема возникнет?

Дополнительная информация:

let totalKm : UInt64 

let data = [UInt8](rowData) // данные содержат [100, 200, 28, 155, 0, 0, 0, 26, 244, 0, 0, 0, 45, 69, 0, 0, 0, 4, 246]

let totalKM_BitsArray = [data[8],data[7],data[6],data[5],data[4]] // содержат [244,26,0,0,0]

self.totalKm = UnsafePointer(totalKM_BitsArray).withMemoryRebound(to:UInt64.self, capacity: 1) {$0.pointee}

// когда журнал печати дает правильное значение, при запуске на устройстве выдает неправильное значение 3544649566089386, как это.

 self.totalKm = byte0 | byte1 | byte2 | byte3 | byte4 

// вывод равен 6900 Это правильно, как и ожидалось

1 Ответ

0 голосов
/ 10 мая 2018

Есть несколько проблем с этим подходом:

let data = [UInt8](rowData) // rowData is type of Data class
let totalKM_BitsArray = [data[8], data[7], data[6], data[5], data[4]]
self.totalKm = UnsafePointer(totalKM_BitsArray)
                 .withMemoryRebound(to:UInt64.self, capacity: 1) { $0.pointee }
  • Разыменование UnsafePointer(totalKM_BitsArray) равно неопределенное поведение , так как указатель на буфер totalKM_BitsArray действителен только временно на время вызова инициализатора (возможно, в какой-то момент в будущем Swift будет предупреждать о таких конструкциях).

  • Вы пытаетесь привязать только 5 экземпляров UInt8 к UInt64, поэтому оставшиеся 3 экземпляра будут мусором.

  • Вы можете только withMemoryRebound(_:) между типами одинакового размера и шага; что не относится к UInt8 и UInt64.

  • Это зависит от порядкового номера вашей платформы; data[8] будет самым младшим байтом на платформе с прямым порядком байтов, но самым значимым байтом на платформе с прямым порядком байтов.

Ваша реализация со сдвигом битов позволяет избежать всех этих проблем (и, как правило, это более безопасный путь, поскольку вам не нужно учитывать такие вещи, как совместимость компоновки, выравнивание и наложение указателей).

Однако, если вы просто хотите дополнить свои данные нулями для самых старших байтов, с rowData[4] до rowData[8], составляющими оставшиеся младшие байты, тогда вам потребуется сдвиг битов реализация должна выглядеть так:

let rowData = Data([
  100, 200, 28, 155, 0, 0, 0, 26, 244, 0, 0, 0, 45, 69, 0, 0, 0, 4, 246
])

let byte0 = UInt64(rowData[4]) << 32
let byte1 = UInt64(rowData[5]) << 24
let byte2 = UInt64(rowData[6]) << 16
let byte3 = UInt64(rowData[7]) << 8
let byte4 = UInt64(rowData[8])
let totalKm = byte0 | byte1 | byte2 | byte3 | byte4
print(totalKm) // 6900

или, итеративно:

var totalKm: UInt64 = 0
for byte in rowData[4 ... 8] {
  totalKm = (totalKm << 8) | UInt64(byte)
}
print(totalKm) // 6900

или, используя reduce(_:_:):

let totalKm = rowData[4 ... 8].reduce(0 as UInt64) { accum, byte in
  (accum << 8) | UInt64(byte)
}
print(totalKm) // 6900

Мы можем даже абстрагировать это в расширение на Data, чтобы упростить загрузку таких целых чисел фиксированной ширины:

enum Endianness {
  case big, little
}

extension Data {
  /// Loads the type `I` from the buffer. If there aren't enough bytes to
  /// represent `I`, the most significant bits are padded with zeros.
  func load<I : FixedWidthInteger>(
    fromByteOffset offset: Int = 0, as type: I.Type, endianness: Endianness = .big
  ) -> I {
    let (wholeBytes, spareBits) = I.bitWidth.quotientAndRemainder(dividingBy: 8)
    let bytesToRead = Swift.min(count, spareBits == 0 ? wholeBytes : wholeBytes + 1)

    let range = startIndex + offset ..< startIndex + offset + bytesToRead
    let bytes: Data
    switch endianness {
    case .big:
      bytes = self[range]
    case .little:
      bytes = Data(self[range].reversed())
    }

    return bytes.reduce(0) { accum, byte in
      (accum << 8) | I(byte)
    }
  }
}

Мы проделываем здесь небольшую дополнительную работу для того, чтобы прочитать правильное количество байтов, а также иметь возможность обрабатывать как большие, так и младшие порядковые номера. Но теперь, когда мы написали это, мы можем просто написать:

let totalKm = rowData[4 ... 8].load(as: UInt64.self)
print(totalKm) // 6900

Обратите внимание, что до сих пор я предполагал, что Data, который вы получаете, имеет нулевую индексацию. Это безопасно для приведенных выше примеров, но не обязательно безопасно в зависимости от того, откуда поступают данные (как это может быть фрагмент). Вы должны быть в состоянии сделать Data(someUnknownDataValue), чтобы получить нулевое индексированное значение данных, с которым вы можете работать, хотя, к сожалению, я не верю, что есть какая-либо документация, которая гарантирует это.

Чтобы обеспечить правильную индексацию произвольного значения Data, вы можете определить следующее расширение, чтобы выполнить правильное смещение в случае, когда вы имеете дело со срезом:

extension Data {
  subscript(offset offset: Int) -> Element {
    get { return self[startIndex + offset] }
    set { self[startIndex + offset] = newValue }
  }

  subscript<R : RangeExpression>(
    offset range: R
  ) -> SubSequence where R.Bound == Index {
    get {
      let concreteRange = range.relative(to: self)
      return self[startIndex + concreteRange.lowerBound ..<
                  startIndex + concreteRange.upperBound]
    }
    set {
      let concreteRange = range.relative(to: self)
      self[startIndex + concreteRange.lowerBound ..<
           startIndex + concreteRange.upperBound] = newValue
    }
  }
}

То, что вы можете использовать, затем назовите, например, data[offset: 4] или data[offset: 4 ... 8].load(as: UInt64.self).


Наконец, стоит отметить, что, хотя вы , возможно, могли бы реализовать это как повторную интерпретацию битов, используя Data withUnsafeBytes(_:) метод:

let rowData = Data([
  100, 200, 28, 155, 0, 0, 0, 26, 244, 0, 0, 0, 45, 69, 0, 0, 0, 4, 246
])

let kmData = Data([0, 0, 0] + rowData[4 ... 8])
let totalKm = kmData.withUnsafeBytes { buffer in
  UInt64(bigEndian: buffer.load(as: UInt64.self))
}
print(totalKm) // 6900

Это полагается на то, что буфер Data выровнен на 64 бита, что не гарантируется. Вы получите ошибку времени выполнения при попытке загрузить смещенное значение, например:

let data = Data([0x01, 0x02, 0x03])
let i = data[1...].withUnsafeBytes { buffer in
  buffer.load(as: UInt16.self) // Fatal error: load from misaligned raw pointer
}

Загружая вместо этого отдельные значения UInt8 и выполняя сдвиг битов, мы можем избежать таких проблем выравнивания (однако, если / когда UnsafeMutableRawPointer поддерживает невыровненные нагрузки , это больше не будет проблемой).

...