Почему LayoutKind.Последовательная работа по-другому, если структура содержит поле DateTime?

почему LayoutKind.Последовательная работа по-другому, если структура содержит поле DateTime?

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

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication3
{
    static class Program
    {
        static void Main()
        {
            Inner test = new Inner();

            unsafe
            {
                Console.WriteLine("Address of struct   = " + ((int)&test).ToString("X"));
                Console.WriteLine("Address of First    = " + ((int)&test.First).ToString("X"));
                Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X"));
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Inner
    {
        public byte First;
        public double NotFirst;
        public DateTime WTF;
    }
}

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

адрес структуры = 40F2CC
Адрес First = 40F2D4
Адрес NotFirst = 40F2CC

обратите внимание, что адрес First не совпадает с адресом адрес структуры; однако адрес NotFirst и то же самое, что и адрес структуры.

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

адрес структуры = 15F2E0
Адрес First = 15F2E0
Адрес NotFirst = 15F2E8

Теперь "Первый" тут имеют тот же адрес, что и структура.

Я нахожу это поведение удивительно, учитывая использование LayoutKind.Последовательный. Может ли кто-нибудь дать объяснение? Имеет ли это поведение какие-либо последствия при взаимодействии со структурами C/C++, использующими тип COM DATETIME?

[EDIT] примечание: Я проверил это при использовании Маршала.StructureToPtr () для маршалирования структуры, данные и выстроены в правильном порядке, причем" первое " поле является первым. Это, похоже, предполагает, что он будет работать нормально с interop. Тайна почему внутренняя компоновка изменяется - но, конечно, внутренняя компоновка никогда не указывается, поэтому компилятор может делать то, что ему нравится.

[EDIT2] удалил "небезопасный" из объявления структуры (это было остатком от некоторого тестирования, которое я делал).

[EDIT3] исходный источник для этого вопроса был из форумов MSDN c#:

http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/fb84bf1d-d9b3-4e91-823e-988257504b30

6 ответов


почему LayoutKind.Последовательная работа по-другому, если структура содержит поле DateTime?

это связано с (удивительно) то, что DateTime сам имеет макет "авто" (Ссылка на такой вопрос самостоятельно). Этот код воспроизводит поведение, которое вы видели:

static class Program
{
    static unsafe void Main()
    {
        Console.WriteLine("64-bit: {0}", Environment.Is64BitProcess);
        Console.WriteLine("Layout of OneField: {0}", typeof(OneField).StructLayoutAttribute.Value);
        Console.WriteLine("Layout of Composite: {0}", typeof(Composite).StructLayoutAttribute.Value);
        Console.WriteLine("Size of Composite: {0}", sizeof(Composite));
        var local = default(Composite);
        Console.WriteLine("L: {0:X}", (long)(&(local.L)));
        Console.WriteLine("M: {0:X}", (long)(&(local.M)));
        Console.WriteLine("N: {0:X}", (long)(&(local.N)));
    }
}

[StructLayout(LayoutKind.Auto)]  // also try removing this attribute
struct OneField
{
    public long X;
}

struct Composite   // has layout Sequential
{
    public byte L;
    public double M;
    public OneField N;
}

пример вывода:

64-bit: True
Layout of OneField: Auto
Layout of Composite: Sequential
Size of Composite: 24
L: 48F050
M: 48F048
N: 48F058

если мы удалим атрибут из OneField, вещи ведут себя так, как ожидалось. Пример:

64-bit: True
Layout of OneField: Sequential
Layout of Composite: Sequential
Size of Composite: 24
L: 48F048
M: 48F050
N: 48F058

эти пример с х64 компиляция платформы (поэтому размер 24, три раза восемь, неудивительно), но и с x86 мы видим те же "неупорядоченные" адреса указателей.

поэтому я думаю, что могу заключить, что макет OneField (респ. DateTime в вашем примере) влияет на компоновку структуры, содержащей OneField член, даже если эта составная структура сама имеет layout Sequential. Я не уверен, что это проблематично (или даже требовать.)


согласно комментарию Ханса Пассанта в другом потоке,он больше не делает попытки сохранить его последовательным когда один из членов является Auto макет структуры.


Go прочитайте спецификацию для правил компоновки более внимательно. правила компоновки управляют компоновкой только тогда, когда объект отображается в неуправляемой памяти. Это означает, что компилятор может свободно размещать поля, пока объект не будет фактически экспортирован. Несколько к моему удивлению, это даже верно для FixedLayout!

Ян Рингроуз прав в вопросах эффективности компилятора, и это тут


чтобы ответить на мои собственные вопросы (как советовали):

вопрос: "имеет ли это поведение какие-либо последствия при взаимодействии со структурами C/C++, которые используют тип COM DATETIME?"

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

вопрос " Может ли кто-нибудь дать объяснение?".

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


несколько факторов

  • двойники намного быстрее, если они выровнены
  • кэши CPU могут работать лучше, если в пораженном

таким образом, компилятор C# имеет несколько недокументированных правил, которые он использует, чтобы попытаться получить"лучшие" макет структуры, эти правила могут учитывать общий размер структуры, и/или если он содержит другую структуру и т. д. Если вам нужно знать расположение структуры, то вы должны укажите его сами, а не позволяйте компилятору решать.

однако LayoutKind.Sequential останавливает компилятор, изменяя порядок полей.


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

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

таким образом, расположение управляемой структуры не влияет на расположение родной структуры. Только атрибуты влияют на расположение родной структуры.

Если бы поля настройки с атрибутами Маршала хранились в управляемых данных так же, как и собственные данные, то в маршале не было бы смысла.StructureToPtr, вы просто байт-скопируйте данные.


Если вы собираетесь взаимодействовать с C/C++, я всегда буду конкретным с StructLayout. Вместо последовательного я бы пошел с Explicit и указал каждую позицию с FieldOffset. Кроме того, добавьте переменную Pack.

[StructLayout(LayoutKind.Explicit, Pack=1, CharSet=CharSet.Unicode)]
public struct Inner
{
    [FieldOffset(0)]
    public byte First;
    [FieldOffset(1)]
    public double NotFirst;
    [FieldOffset(9)]
    public DateTime WTF;
}

похоже, что DateTime не может быть Маршалирован в любом случае, только в строку (Bingle Marshal DateTime).

переменная Pack особенно важна в коде C++, который может быть скомпилирован в разных системах с разным словом размеры.

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