Есть ли в F# функции адресации & и разыменования указателя *?

На С# я пишу

unsafe void Main() {
  float f = 3.14f;
  int i = *(int*)&f;
}

Можно ли перевести этот код на F#?

Насколько я понимаю, указатель представлен типом nativeptr<'a>, но я не могу найти эквивалента адресной ссылки & и операторов разыменования указателя *. подозреваю, что NativePtr.get может быть последним, но ее реализация ускользает от меня, так как я недостаточно хорошо понимаю IL.

Я знаю о BitConverter и Marshal, но ищу способ реализовать изменение битов без копирования памяти.


person kkm    schedule 22.03.2016    source источник
comment
@GuyCoder: я хочу посмотреть на биты существующего числа с плавающей запятой, как если бы оно было целым числом.   -  person kkm    schedule 23.03.2016
comment
Какова цель этого? Почти наверняка писать такой код — плохая идея.   -  person John Palmer    schedule 23.03.2016
comment
@JohnPalmer: это совершенно не имеет отношения к вопросу, но цель - хеширование.   -  person kkm    schedule 23.03.2016
comment
@GuyCoder: Пожалуйста, не могли бы вы воздержаться от личных нападок на мои намерения, особенно не зная их заранее?   -  person kkm    schedule 23.03.2016
comment
Сведения об операциях nativeptr см. на странице msdn.microsoft.com/en-us/library/ee340434. aspx   -  person John Palmer    schedule 23.03.2016
comment
Я не нападаю, я согласен. Я сделал прототип дизассемблера на F#, так что это знакомо. Я помню, что у меня была похожая проблема, но я думаю, что мне, наконец, пришлось пойти на сортировку, чтобы пройти проверку типов.   -  person Guy Coder    schedule 23.03.2016
comment
@JohmPalmer: Спасибо, это именно тот класс, на который я ссылаюсь в своем собственном вопросе. Так является ли NativePtr.get правильной заменой C# *, когда 'T является примитивным типом значения? Я понятия не имею, что такое ldobj.   -  person kkm    schedule 23.03.2016
comment
@GuyCoder: Извините, я, должно быть, неправильно вас понял, без обид Да, звучит странно, но мне нужно побит-хешировать некоторые внутренние коллекции. в Bitconverter есть DoubleToInt64Bits (который мне не нужен), но нет SingleToInt32Bits (который мне нужен, и который не более чем *((int*)&value. Еще глупее было бы создать отдельную библиотеку C# только для этой единственной функции!   -  person kkm    schedule 23.03.2016
comment
Я тоже не знаю, что делает ldobj. Вы всегда можете проверить это с целыми числами / байтами / чем-то простым или даже написать программу сравнения для сравнения с C. Сказав это, get довольно разработан, чтобы притворяться, что ptr указывает на массив.   -  person John Palmer    schedule 23.03.2016
comment
@JohnPalmer: Да, действительно, я мог бы проверить это, но на данный момент я не нашел кандидата для другой операции, &. Без него я могу стрелять только случайными адресами с NativePtr.get :)   -  person kkm    schedule 23.03.2016
comment
@GuyCoder: Интересный подход, спасибо. Это практически решило бы мою первоначальную проблему (я думаю, JIT-компилятор даже оптимизировал копирование дополнительных значений в стеке при создании этого объекта, так что вряд ли будет даже один удар по производительности цикла), но вопрос об эквивалентах F # & и * все еще другое. Так что нет, не дубликат.   -  person kkm    schedule 23.03.2016
comment
Представляет интерес: Как создать тип Union в F#, который является типом значения?   -  person Guy Coder    schedule 23.03.2016
comment
.NET теперь имеет официальный API для этого nuget.org/packages/System.Runtime .CompilerServices.Unsafe   -  person Dzmitry Lahoda    schedule 01.10.2018


Ответы (1)


Функции NativePtr.get и set читают и записывают со смещением. Если вам нужно читать байт за байтом, используйте это. Если вам нужно читать с нулевым смещением, вы можете вместо этого использовать read и write, они будут немного более производительными.

Оператор для получения «сырого» адреса (в отличие от ссылки byref<_>) называется && (см. определение).

Но есть еще некоторые хитрости, такие как: вам нужно пометить переменную mutable, прежде чем вы сможете получить ее адрес, вы не можете просто сохранить значение nativeptr<_>, вам нужно преобразовать его в nativeint, плюс значения nativeptr<_> строго типизированы , поэтому нужно конвертировать между ними через nativeint и т.д.

Следующий фрагмент будет эквивалентен вашему коду C# (пошагово и с аннотациями полного типа для большей ясности):

open FSharp.NativeInterop

let Main() =
  let mutable x: float = 3.1415
  let floatPtr: nativeint = NativePtr.toNativeInt<float> &&x
  let intPtr: nativeptr<int> = floatPtr |> NativePtr.ofNativeInt<int>
  let asInt: int = NativeInterop.NativePtr.read intPtr
  asInt

Или более компактный вариант:

open FSharp.NativeInterop

let Main() =
  let mutable x = 3.1415
  &&x |> NativePtr.toNativeInt |> NativePtr.ofNativeInt |> NativePtr.read<int>

Или упакуйте его для повторного использования:

// val inline readAs : x:'a -> 'b when 'a : unmanaged and 'b : unmanaged
let inline readAs (x: 'a) : 'b =
  let mutable x' = x
  &&x' |> NativePtr.toNativeInt |> NativePtr.ofNativeInt |> NativePtr.read<'b>

let Main() =
  let i = readAs 3.1415 : int
  ()

Сказав все вышесказанное, я полностью согласен с Джоном Палмером и ГайКодером: пожалуйста, не делайте этого, если это вообще возможно. Это похоже на преждевременную оптимизацию, о которой нас предупреждал доктор Кнут.

person Fyodor Soikin    schedule 22.03.2016
comment
Я согласен, что не следует этого делать, если они не знают, что делают. Тем не менее, есть очень веские причины сделать это, если вы знаете, что делаете, и вам это нужно. Если вам нужны указатели, посмотрите SafeHandles. Если вам нужно выполнить много низкоуровневых манипуляций, посмотрите System.Runtime.InteropServices - person Guy Coder; 23.03.2016
comment
Вот оно, спасибо! Очень некрасиво, но делает свое дело. Насчет того, зачем мне это нужно, скопирую свой комментарий сверху: «[...]Мне нужно побит-хешировать некоторые внутренние коллекции. В BitConverter есть DoubleToInt64Bits (которое мне не нужно), но нет SingleToInt32Bits (которое мне нужно, и которого не более *((int*)&value. Было бы еще глупее, на мой вкус, создавать отдельную библиотеку C# только для этой единственной функции!» Действительно , Я люблю функциональные и чистые вещи, но иногда эти грязные кусочки все еще мешают. - person kkm; 23.03.2016
comment
Да, кстати, действительно ли здесь оправдано использование inline и почему? В объявлении нет статических типов каретки (^T вместо 'T). Я пропустил еще один вариант использования ключевого слова inline? - person kkm; 23.03.2016
comment
Нет, inline строго говоря не требуется. Однако, если вам нужна производительность, дополнительный вызов функции имеет большое значение. Я ошибочно предположил, что вы стремитесь к производительности, потому что вы запрашивали арифметику указателя. - person Fyodor Soikin; 23.03.2016
comment
Обратите внимание, что float в C# называется float32 в F#. - person kvb; 23.03.2016
comment
@FyodorSoikin, спасибо. Я на самом деле, и это, возможно, еще один пользовательский случай. Спасибо! - person kkm; 24.03.2016
comment
Не могли бы вы объяснить мне, что такое x 'и 'x, я новичок в F #. - person Hasan A Yousef; 02.02.2018
comment
@HassanAYousef x' — это просто идентификатор. Галочка (также известная как штрих) может быть частью идентификаторов в F#, точно так же, как числа и подчеркивание. 'x — это параметр универсального типа. - person Fyodor Soikin; 02.02.2018