withUnsafeBytes + Поведение общего типа - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть функция, которая позволяет мне читать число (Integer, Double и т. Д.) Из двоичного файла, используя универсальные типы. Например, если я ожидаю Int64, il будет читать 8 байт ...

// A simple function that read n bytes from a FileHandle and returns
// the data

public func read(chunkSize: Int) -> Data {
   return self.handle!.readData(ofLength: chunkSize)
}

// A function that reads the proper amount of bytes specified
// by the return type which in my case would be an integer 

public func readNumber<I>() -> I? {
   let data: Data = self.read(chunkSize: MemoryLayout<I>.size)
   if data.count == 0 {
       return nil
   }
   return data.withUnsafeBytes { $0.pointee }
}

readNumber случайным образом возвращает ноль без причины. Не из проверки count, а из последней строки.

Тем не менее, он прекрасно работает, когда я кастую в I примерно так:

return data.withUnsafeBytes { $0.pointee } as I

Почему это?

РЕДАКТИРОВАТЬ:

Я воспроизвел это, используя игровые площадки:

class Test {

    public func read(chunkSize: Int) -> Data {
        return Data(repeating: 1, count: chunkSize)
    }

    public func readNumber<T>() -> T? {
        let data: Data = read(chunkSize: MemoryLayout<T>.size)
        if data.count == 0 {
            return nil
        }
        return data.withUnsafeBytes { $0.pointee }
    }

    public func example() {
        let value2: Double = readNumber()!
        print(value2)
    }
}

let test = Test()

for i in 0..<1000 {
    test.example()
}

1 Ответ

0 голосов
/ 07 ноября 2018

Кажется, мне нужно немного исправить свой комментарий. Даже если Swift работает согласованно, как запрограммировано, результат может показаться случайным образом изменяющимся, если у вас есть проблемы с памятью, такие как доступ за пределы.

Сначала подготовьте магическое расширение для UnsafePointer:

extension UnsafePointer {
    var printingPointee: Pointee {
        print(Pointee.self) //<- Check how Swift inferred `Pointee`
        return self.pointee
    }
}

И немного измените код EDIT :

class Test {

    public func read(chunkSize: Int) -> Data {
        return Data(repeating: 1, count: chunkSize)
    }

    public func readNumber<T>() -> T? {
        let data: Data = read(chunkSize: MemoryLayout<T>.size)
        if data.count == 0 {
            return nil
        }
        print(T.self) //<- Check how Swift inferred `T`
        return data.withUnsafeBytes { $0.printingPointee }
    }

    public func example() {
        let value2: Double = readNumber()!
        print(value2)
    }
}

let test = Test()

for _ in 0..<1000 {
    test.example()
}

Выход:

Double
Optional<Double>
7.748604185489348e-304
Double
Optional<Double>

Тема 1: Неустранимая ошибка: неожиданно обнаружен ноль при развертывании Необязательное значение

Сколько показанных пар Double и Optional<Double> будет казаться случайным, но причина такого поведения совершенно ясна.

В этой строке return data.withUnsafeBytes { $0.printingPointee } Swift выводит тип $0 как UnsafePointer<Optional<Double>>.

В текущей реализации Swift Optional<Double> занимает 9 байтов в памяти:

print(MemoryLayout<Optional<Double>>.size) //-> 9

Итак, $0.pointee получает доступ к 9 байтам, начиная с указателя, хотя указатель указывает на область из 8 байт:

|+0|+1|+2|+3|+4|+5|+6|+7|+8|
+--+--+--+--+--+--+--+--+
 01 01 01 01 01 01 01 01 ??
 <-taken from the Data->

Как вы знаете, дополнительный 9-й (+8) байт не может быть предсказуемым и может казаться случайным, что является показателем nil в Optional<Double>.

Точно такой же вывод работает в вашем коде. В вашем readNumber<T>() тип возврата четко объявлен как T?, поэтому в строке return data.withUnsafeBytes { $0.pointee } вполне естественно, что Swift выводит тип $0.pointee как Double? aka Optional<Double>.

Вы знаете, что вы можете управлять выводом этого типа, добавив as T.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...