Действительно ли «блитируемые типы» являются неуправляемыми типами для StructLayout Sequential?

Обновлено: Мой вопрос не о разнице между blittable и неуправляемым, а о том, что именно означает документация для StructLayoutAttribute. В этом выпуске от 2022 года до сих пор можно увидеть термин «блитируемый», а не «неуправляемый».

В официальной документации для StructLayoutAttribute говорится:

Для преобразуемых типов LayoutKind.Sequential управляет как макетом, так и в управляемой памяти и макет в неуправляемой памяти. Для непреобразуемые типы, он управляет макетом, когда класс или структура маршалируется в неуправляемый код, но не контролирует расположение в управляемой памяти.

Из этого списка Преобразуемые и непреобразуемые типы, System.Boolean является непреобразуемым.

Однако следующая структура будет иметь последовательный макет в управляемой памяти, хотя она содержит непреобразуемое (но неуправляемое) поле bool.

[StructLayout(LayoutKind.Sequential)]
struct Unmanaged {
    public byte b1;
    public int i1;
    public bool bool1;
    public byte b2;
    //public string str; // -> uncomment to have auto layout

    public unsafe override string ToString() {

        //https://forum.unity.com/threads/question-about-size-and-padding-of-a-type-in-c.1274090/
        var b1FieldOffset = (long)Unsafe.AsPointer(ref this.b1) - (long)Unsafe.AsPointer(ref this);
        var i1FieldOffset = (long)Unsafe.AsPointer(ref this.i1) - (long)Unsafe.AsPointer(ref this);
        var b2FieldOffset = (long)Unsafe.AsPointer(ref this.b2) - (long)Unsafe.AsPointer(ref this);
        var bool1Offset = (long)Unsafe.AsPointer(ref this.bool1) - (long)Unsafe.AsPointer(ref this);

        var sb = new StringBuilder();
        sb.AppendLine($"Size:      {Unsafe.SizeOf<Unmanaged>()}");
        sb.AppendLine($"b1 Offset: {b1FieldOffset}");
        sb.AppendLine($"i1 Offset: {i1FieldOffset}");
        sb.AppendLine($"bool1 Offset: {bool1Offset}");
        sb.AppendLine($"b2 Offset: {b2FieldOffset}");

        return sb.ToString();
    }
}

ToString выведет:

Size:      12
b1 Offset: 0
i1 Offset: 4
bool1 Offset: 8
b2 Offset: 9

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

Size:      16
b1 Offset: 12
i1 Offset: 8
bool1 Offset: 13
b2 Offset: 14

После того, как я уже предоставил отзыв об устаревшем руководстве для поля Pack в той же документации (десятичное число составляет 2 4-байтовых поля и 1 8-байтовое поле в .NET5+, а не 4 4-байтовых поля), я подозреваю, что документация, касающаяся blittable/non-blittable, может быть устарело, и на самом деле это означает, что это неуправляемый/управляемый для Неуправляемые типы

Должен ли я опубликовать еще один отзыв, чтобы документация изменилась, или мои подозрения необоснованны?

@Charlieface, как этот дубликат? Тот, на который вы указали, просто обсуждает семантические различия между терминами и не приводит к реальному выводу. Я спрашиваю о.р.т. StructLayout, который на практике ведет себя так, как будто означает неуправляемый, но на самом деле он не связан с предложением языка. Также есть отличие: структура с примитивными полями с автоматическим макетом здесь «непреобразуемая».

Ivan Petrov 01.04.2024 10:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы ответить на свой вопрос, мне пришлось глубоко покопаться в исходном коде среды выполнения , обвинить определенные версии, прочитать множество проблем на GitHub, комментарии и поэкспериментировать с разными версиями .NET (от 5 до 7).

На данный момент единственной реальной документацией, к сожалению, является исходный код. Стандарт ECMA 335 — Common Language Infrastructure (CLI) последняя редакция датируется 2012 годом, при этом не планируется выпуск версии 7 , которая включает « Дополнение к спецификации ECMA-335 CLI»

В дополнении нет упоминания о расположении классов/структур, поэтому последним официальным источником информации о том, как все должно работать, является 6-е издание ECMA-335 от 2012 года. Соответствующие части метаданных макета последовательного типа:

I.9.5 Расположение классов

sequentiallayout: класс с пометкой sequentiallayout направляет загрузчик сохранить порядок полей в том виде, в каком он был задан, но в противном случае конкретные смещения рассчитываются на основе типа CLI поля; это могут быть сдвигается с помощью явной информации о смещении, заполнении и/или выравнивании.

II.10.1.2 Атрибуты макета типа

последовательный: CLI должен располагать поля в последовательном порядке, на основе порядка полей в логической таблице метаданных (п. II.22.15).

и более общий II.10.7 Управление макетом экземпляра

[Примечание. Метаданные, управляющие макетом экземпляра, не являются «подсказкой», это неотъемлемая часть VES, которая должна поддерживаться всеми соответствующими реализации CLI. последнее примечание]

Никакого различия в размещении в управляемой и неуправляемой памяти не делается. На этом этапе нам придется вернуться к документации из моего вопроса:

Для преобразуемых типов LayoutKind.Sequential управляет как макетом в управляемая память и расположение в неуправляемой памяти. Для неблитируемых типы, он управляет макетом при маршалировании класса или структуры. к неуправляемому коду, но не контролирует расположение в управляемой памяти

Очевидно, это одинаково для всех версий .NET (5,6,7,8). По моему мнению, последнее противоречит тому, что диктует ECMA 335.

Наиболее подходящий исходный код для этого, который я нашел, находится в runtime/src/coreclr/vm/classlayoutinfo.cpp, а точнее, в функции

DetermineBlittabilityAndManagedSequential

В нем код проходит через поля структуры/класса и проверяет, являются ли они blitable или они лишат нашего типа возможности последовательного управления (чего мы и добиваемся). Логика .NET 5 и .NET 6 отличается от логики .NET 7 (опять же недокументирована).

В .NET 5/6

 *fDisqualifyFromManagedSequential |= CheckIfDisqualifiedFromManagedSequential(corElemType, fsig, &pFieldInfoArrayOut->m_placement);

Это наиболее важные части функции:

BOOL CheckIfDisqualifiedFromManagedSequential(CorElementType corElemType, MetaSig& fsig, RawFieldPlacementInfo* pManagedPlacementInfo) {

if (CorTypeInfo::IsPrimitiveType(corElemType))
else if (corElemType == ELEMENT_TYPE_PTR)
else if (corElemType == ELEMENT_TYPE_VALUETYPE)
{
    return !pNestedType.GetMethodTable()->IsManagedSequential();
}

Если поле типа представляет собой структуру, которая сама по себе не управляется последовательно, это дисквалифицирует тип для последовательного управления. Это в некоторой степени логичное, но совершенно недокументированное поведение (помните, что в документации выше это вообще не упоминается для версий .NET 5 и 6). Мы можем проверить это с помощью следующего кода

[StructLayout(LayoutKind.Sequential)]
struct Unmanaged {
    public bool bool1;
    public char ch;
    public int i1;
    public byte b2;
    public AutoStruct autostruct;

    public unsafe override string ToString() {

        var bool1Offset = (long)Unsafe.AsPointer(ref this.bool1) - (long)Unsafe.AsPointer(ref this);
        var chFieldOFfset = (long)Unsafe.AsPointer(ref this.ch) - (long)Unsafe.AsPointer(ref this);
        var i1FieldOffset = (long)Unsafe.AsPointer(ref this.i1) - (long)Unsafe.AsPointer(ref this);
        var b2FieldOffset = (long)Unsafe.AsPointer(ref this.b2) - (long)Unsafe.AsPointer(ref this);
        var autoStructFieldOffset = (long)Unsafe.AsPointer(ref this.autostruct) - (long)Unsafe.AsPointer(ref this);

        var sb = new StringBuilder();
        sb.AppendLine($"Size:      {Unsafe.SizeOf<Unmanaged>()}");
        sb.AppendLine($"bool1 Offset: {bool1Offset}");
        sb.AppendLine($"ch Offset: {chFieldOFfset}");
        sb.AppendLine($"i1 Offset: {i1FieldOffset}");
        sb.AppendLine($"b2 Offset: {b2FieldOffset}");
        sb.AppendLine($"AutoStruct Offset: {autoStructFieldOffset}");


        return sb.ToString();
    }
}

[StructLayout(LayoutKind.Auto)]
struct AutoStruct {
    byte b1;
    int i1;
}   

Вызов ToString вернет это в .NET5/6 (рекомендуется запустить в LinqPad для легкого переключения между различными средами выполнения)

Size:      16
bool1 Offset: 6
ch Offset: 4
i1 Offset: 0
b2 Offset: 7
AutoStruct Offset: 8

Обратите внимание, что у нас нет последовательного макета, поскольку AutoStruct дисквалифицирует нашу неуправляемую структуру и CLR берет на себя управление.

Однако в .NET7+ мы получаем «ожидаемое поведение».

Size:      24
bool1 Offset: 0
ch Offset: 2
i1 Offset: 4
b2 Offset: 8
AutoStruct Offset: 16

Изменение связано с тем, что проверка ManagedSequential теперь этот код

*fDisqualifyFromManagedSequential |= TypeHasGCPointers(corElemType, typeHandleMaybe);

и полный код того, что означает «blittable», он же управляемо-последовательный.

BOOL TypeHasGCPointers(CorElementType corElemType, TypeHandle pNestedType)
{
    if (CorTypeInfo::IsPrimitiveType(corElemType) || corElemType == ELEMENT_TYPE_PTR || corElemType == ELEMENT_TYPE_FNPTR)
    {
        return FALSE;
    }
    if (corElemType == ELEMENT_TYPE_VALUETYPE)
    {
        _ASSERTE(!pNestedType.IsNull());
        return pNestedType.GetMethodTable()->ContainsPointers() != FALSE;
    }
    return TRUE;
}

Похоже, ManagedSequntial, также известный как blittable (в документации), аналогичен неуправляемому (функция Csharp) для .NET7+.

Другие вопросы по теме