Почему кажется, что эта строка хранится встроенным по значению в явном классе или структуре макета? - PullRequest
0 голосов
/ 06 января 2019

Я делал несколько крайне небезопасных и немного бесполезных действий с пакетом System.Runtime.CompilerServices.Unsafe MSIL, который позволяет вам многое делать с указателями, которые вы не можете использовать в C #. Я создал метод расширения, который возвращает ref byte, причем этот байт является началом указателя таблицы методов в начале объекта, что позволяет использовать любой объект в фиксированной инструкции, переводя байтовый указатель в начало объект:

public static unsafe ref byte GetPinnableReference(this object obj)
{
    return ref *(byte*)*(void**)Unsafe.AsPointer(ref obj);
}

Затем я решил проверить это, используя этот код:

[StructLayout(LayoutKind.Explicit, Pack = 0)]
public class Foo
{
    [FieldOffset(0)]
    public string Name = "THIS IS A STRING";
}

[StructLayout(LayoutKind.Explicit, Pack = 0)]
public struct Bar
{
    [FieldOffset(0)]
    public string Name;
}

А потом в методе

        var foo = new Foo();
        //var foo = new Bar { Name = "THIS IS A STRING" };

        fixed (byte* objPtr = foo)
        {
            char* stringPtr = (char*)(objPtr + (foo is Foo ?  : 12));

            for (var i = 0; i < foo.Name.Length; i++)
            {
                Console.Write(*(stringPtr + i /* Char offset */));
            }

            Console.WriteLine();
        }

        Console.ReadKey();

Действительно странная вещь об этом - то, что это успешно печатает "ЭТО СТРОКА"? Код работает так:

  1. Получить байтовый указатель, objPtr, к самому началу объекта
  2. Добавьте 16, чтобы получить фактические данные
  3. Добавьте еще 16, чтобы пройти заголовок строки к фактическим данным строки.
  4. Добавьте 4, чтобы пропустить первые 4 байта строки, которые являются int _stringLength (выставлено нам как свойство Length)
  5. интерпретировать результат как указатель на символ

EDIT: Важный момент - при переключении foo на тип Bar я добавляю только 12, а не 36 байтов (36 = 16 + 16 + 4). Почему в структуре только 8 байтов заголовка, а не 32 в классе? Было бы разумно, чтобы структура имела меньший заголовок (я не думаю, что syncblk), но тогда почему строка не имеет 16-байтовой головы? Я ожидаю, что смещение будет 8 + 16 + 4 (28), а не 8 + 4 (12) Однако это предположение делает большой недостаток. Предполагается, что строка хранится внутри class/struct. Однако строки являются ссылочными типами, и только моя ссылка на них хранится внутри объекта. В частности, я думал, что ссылочные типы могут быть помещены в кучу только - и поскольку эта структура является локальной переменной, я думал, что она находится в стеке. Если бы это было не так, код наверняка выглядел бы примерно так, чтобы получить stringPtr

byte** stringRefptr = objPtr + 16;
char* stringPtr = (char*)(*stringRefPtr + 20);

где вы берете строковую ссылку как byte** и затем используете ее, чтобы добраться до символов. И это все равно не имело бы смысла, если бы внутренняя строка была char[] (я не уверен, что это так)

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

ПРИМЕЧАНИЕ. Требуется .NET Core 2.0+ с пакетом System.Runtime.CompilerServices.Unsafe nuGet и C # 7.3+.

1 Ответ

0 голосов
/ 02 февраля 2019

Потому что строки действительно хранятся в строке. Проблема с вашим предположением состоит в том, что строки не являются обычными объектами, а обрабатываются CLR как особый случай (вероятно, по соображениям производительности).

А что касается объектов, так как строка является единственным членом, то, естественно, это был бы наиболее эффективный способ выделения памяти. Попробуйте добавить больше членов после вашего строкового члена, и ваш код сломается.

Вот несколько ссылок на то, как строки хранятся в CLR

https://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/

https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/

Редактировать: Я не проверял, но я полагаю, что ваши рассуждения о смещениях отключены. 36 = 24 (размер объекта) + 8 (заголовок строки?) + 4 (размер целого), в то время как для структуры 24 байта становится 0, поскольку у него нет заголовка.

...