Structlayout ведет себя некорректно в отладочной сборке - PullRequest
3 голосов
/ 03 июля 2019

Рассмотрим следующую программу:

using System.Runtime.InteropServices;
using System;
public class Program
{
    public static void Main()
    {
        new magic
        {
            S = "Hello",
            C =
            {
                [0] = 'W',
                [1] = 'o',
                [2] = 'r',
                [3] = 'l',
                [4] = 'd',
            }
        };

        Console.WriteLine("Hello");
        Console.ReadKey();    
    }

    [StructLayout(LayoutKind.Explicit)]
    struct magic
    {
        [FieldOffset(0)]
        public string S;

        [FieldOffset(0)]
        public char[] C;
    }
}

Почему это печатает HeWor при сборке с отладкой и World (как и ожидалось) при выпуске? Протестировано на VS 2019 с .net 4.8

Я знаю, что это больше, чем граница, но есть ли какое-то объяснение этому?

1 Ответ

4 голосов
/ 03 июля 2019

Для этого нет действительного «ожидаемого» результата; поведение совершенно не определено; происходит то, что вы присваиваете строковую ссылку на S (что, очевидно, также C), затем лежит и говорите с кодом индексатора массива, но выполняете против экземпляра строки. Поскольку это интернированный "Hello", вы перезаписываете глобальный интернированный "Hello", но: код операции индексатора массива знает только, как общаться с массивами, и, таким образом, получает неправильное смещение . Внутреннее расположение строки и массива может быть разным (и, разумеется, в зависимости от времени выполнения, версии фреймворка и т. Д.), Поэтому оно может (и явно делает) обновлять байты, начиная с неправильного смещения от заголовка объекта.

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

Если вы хотите правильно получить смещения, тогда либо используйте fixed, либо ToSpan() / ToMemory(). Первый позволяет трактовать string как char*; вторая позволяет рассматривать string как ReadOnlySpan<char> (но вы можете использовать MemoryMarshal, чтобы повысить ReadOnlyMemory<char> до Memory<char>).

Примеры:

Console.WriteLine("Hello"); // Hello

// note: using MemoryMarshal.* is like using Unsafe.*; you
// are explicitly accepting the consequences if used incorrectly
var span = MemoryMarshal.AsMemory("Hello".AsMemory()).Span;
span[0] = 'W';
span[1] = 'o';
span[2] = 'r';
span[3] = 'l';
span[4] = 'd';
Console.WriteLine("Hello"); // World

// ditto, "unsafe" means you're accepting the consequences
fixed(char* c = "Hello")
{
    c[0] = 'd';
    c[1] = 'l';
    c[2] = 'r';
    c[3] = 'o';
    c[4] = 'W';
}
Console.WriteLine("Hello"); // dlroW

Кроме того, предположительно, действительно не нужно говорить, но ... не делайте этого!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...