Pull to refresh

Comments 20

пока вы не решите посчитать хеш, сделать двоичное сравнение или сохранить структуру в бинарном виде, например, в файл.

А зачем я буду решать это всё делать с неупакованными структурами?

Из плюсовой dll пришел массив структур, и у них паддинги набиты мусором. А нужно посчитать хеш, например, такой либой. И тут два варианта: или копировать их в новый массив, при чём нужно будет написать логику этого копирования, используя схожие техники, описанные мной в статье. Или обнулить паддинги. Это один пример из тысяч, зачем это нужно делать.

Паддинг - это такая implementation-defined штука: сегодня там три байта между полями, завтра компилятор обновился и стало семь. Такие структуры в принципе нельзя шарить между разными платформами. Разве только если любите стрелять себе по ногам. Выкиньте эту плюсовую либу и замените на кросс-платформенную.

C# ли бы тоже выкинуть, где сделали что-то подобное? Или собственный код, где такой подход даёт ускорение, т.к. создаёт очень много таких структур и часто. А считать хеш нужно очень редко.

[SkipLocalsInit]
ExampleStruct[] Method0()
{
    Span<ExampleStruct> arr = stackalloc ExampleStruct[10];

    for (var i = 0; i < arr.Length; i++)
    {
        ref var s = ref arr[i];
        s.A = (byte) i;
        s.B = i * 10;
    }

    return arr.ToArray();
}

struct ExampleStruct
{
    public byte A;
    public int B;
}

Если вам важна разница в производительности между zero-initialized структурами и SkipLocalsInit, то ваш кейс уже довольно специфичен, а не один из тысяч (вы ведь провели профилирование кода и уверены, что это необходимая оптимизация, верно?).

А считать хеш нужно очень редко

Тогда хешу можно скормить данные и поле за полем, ручками.

А можно написать либу один раз и не писать ручками. Я не очень понимаю, о чём спор. Если вам не нужно, это не означает, что никому не нужно. Мы же тут программированием занимаемся. Если что-то можно автоматизировать, оно должно быть автоматизировано.

Я не очень понимаю, о чём спор.

О том, что если вы решаете задачу из статьи, вы почти наверняка что-то делаете не так.

  1. Если вы не используете unsafe код, вы никогда не столкнетесь с никакими багами из-за мусора в паддинга (он там может быть даже без интеропа, просто со стека)

  2. Абсолютно бессмысленнго его занулять для структур без StructLayout.Explicit, потому что JIT может легко структуру с зануллеными падингами временно куда-то скопировать на стек только полями и будет опять мусор со стека в полях, короче говоря, это UB.

  3. Никогда не пишите код который считает хэш, сравнивает структуры через memcpy/memcmp идиомы, это отличный способ выстрелить себе в ногу с гранатомета. Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить (+ может быть различие на разных архитектурах/платформах)

Рекомендую к прочтению статью.

Если структура лежит в массиве или где-то внутри класса, JIT ничего с ней не сделает. Хеш массива помогает мгновенно понять, были ли в нём изменения. И если не было, то пропустить огромное количество логики. Полезно, например, в играх, где нужно каждый кадр пересчитывать состояние мира. Или когда нужно часто писать в файл, и вместо насилия над диском проще сравнивать хеш и писать только когда реально изменились данные.

Вашу задачу невозможно решить в общем случае без около-UB люто unsafe кода (который обязательно выстрелит в виде CVE) в .NET. Как я вам уже написал: Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить

Я привёл пример с записью в файл. Проверка хеша, если не совпали, то идёт запись. Какая тут уязвимость может появиться? В худшем случае при несовпадении байт файл будет перезаписан, когда реальные данные не изменились. В примере с игрой в худшем случае будет выполнена тяжёлая логика лишний раз, но без проверки по хешу пришлось бы каждый кадр тратить большие ресурсы на эту же проверку. Естественно, что надо понимать, для чего используется подобный инструмент и какие опасности он несёт. Но это касается любых вещей. Очевидно, что подобный хеш не должен уйти в долговременное хранение, он предназначен для здесь и сейчас, для текущей сессии.

Мой поинт в том что вы рекламируете это как библиотеку и у вас ни слова нет про то насколько это хрупкий/ненадежный около-UB/unsafe инструмент. Что бы вы понимали, бинарные сериализаторы, которые делают похожие хаки сериализуя объекты в байт массивы (и десереиализуя) без учета паддингов были не раз причиной CVE.

В моём коде нет никакого около-UB. Паддинги определяются через рефлексию здесь и сейчас. Генерация метода через IL - вполне официальный способ, а не хак. Остаётся лишь вопрос к тому, кто будет применять инструмент. Но это риторический вопрос. Я совершенно точно не в ответе за тех, кто пытается сделать подобные вещи без понимания их сути.

А вы точно уверены, что хэш массива структур (не байт) будет считаться как хэш области памяти, занимаемой массивом?

Если так, то у меня плохие новости для разработчиков (и пользователей) такой библиотеки вычисления хэша :)

Библиотека из статьи вообще не считает хеши. О чём речь вообще, для кого плохие новости?

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

Ну вот у меня и возник вопрос, точно ли библиотеки, которые считают хэши, поступают именно так, как вы пишите? Т.е. интерпретируют массив структур, как непрерывную область памяти, и считают хэш от получившегося байтового буфера?

Вот, можете ознакомится с исходниками: https://github.com/viruseg/GxHash.128bits.Overloads

Т.е. интерпретируют массив структур, как непрерывную область памяти, и считают хэш от получившегося байтового буфера?

Всё так.

Так это же ваша библиотека )

А у других вы такое видели? В Майкрософтовских из System.Security.Cryptography, например?

Другие я даже не искал. Потому что использую именно GxHash, т.к. он очень быстрый. Поэтому и написал для него обертку. Уверен, что если кто-то ставил цель получить хеш массива структур, то решил эту задачу точно так же. Span используется повсеместно.

Забавно что вы написали небезопасный хэшер который считает с мусором в хеше, и потом героически решили это проблему через другую библиотеку, которая в комплект не входит :D кстати у вас там явно баг в коде https://github.com/viruseg/GxHash.128bits.Overloads/blob/master/GxHash.128bits.Overloads/Hash128Methods.cs#L190 - вместо buffer.Length должно было видимо бы asBytes.Length

Sign up to leave a comment.

Articles