Как. Net время выполнения "понимает", что структура все еще имеет ссылку - PullRequest
2 голосов
/ 14 февраля 2020

Вопрос в том, как. Net среда выполнения понимает, что поля структуры, которая была помещена в память с помощью Marshal.StructureToPtr, не должны быть освобождены G C.

Ниже сценария.

У меня есть следующая структура:

[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct
{
    public string s;
    public Stream stream;

    public SomeStruct(string s)
    {
        this.s = s;
        this.stream = new MemoryStream(0x100);
    }
}

Существует метод, который создает экземпляр структуры и помещает ее в память:

static IntPtr GetStructRawData()
{
    IntPtr ptr = Marshal.AllocHGlobal(1024);
    Marshal.StructureToPtr(new SomeStruct("hi"), ptr, false);
    return ptr;
}

Затем я могу создать новую структуру из необработанная память:

IntPtr ptr = GetStructRawData();

GC.Collect();

SomeStruct struct2 = (SomeStruct)Marshal.PtrToStructure(ptr, typeof(SomeStruct));

После того, как struct2 действительно содержит правильную строку ("привет") и правильный поток. Таким образом, кажется, что есть ссылки на эту строку и на этот поток struct1. Но что держит ссылки? Как среда выполнения понимает, что строка и поток не должны быть собраны?

Ответы [ 2 ]

4 голосов
/ 14 февраля 2020

Но что держит ссылки? Как среда выполнения понимает, что строка и поток не должны быть собраны?

string здесь является частным случаем; на самом деле это интернированная строка (загруженная через ldstr), поэтому она уже укоренена в таблице интернов.

Однако MemoryStream ... откровенно говоря, это не корень . Ваш код по своей сути сломан и опасен, и в любой момент он может ужасно потерпеть неудачу. Объекты могут быть собраны или перемещены (сжатие) в любое время, и это приведет к тому, что в неуправляемой памяти останутся неработающие ссылки, потому что G C не смотрит на неуправляемую память .

Я считаю, что ваш код только «работает» в настоящее время, потому что G C не был агрессивен с вами. Также имейте в виду: G C не стирает объекты; если G C просто решил считать MemoryStream собранным, вы все равно сможете снова поговорить с ним , не пожаловавшись , если память все еще выглядит нормально для какое-то время. Но это работает по неправильным причинам.

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

Если вы собираетесь использовать неуправляемую память, ограничение where T : unmanaged может быть спасением для вас. Это мешает вам войти в этот сценарий, но как необходимость ограничивает то, что вы можете сделать. Значение: вы не можете иметь эти поля.

3 голосов
/ 14 февраля 2020
Комментарий

за март c Я обновил свой ответ.

Я немного изменил вашу программу и посмотрел на нее в WinDbg, чтобы лучше ее понять. В вашем примере литеральная строка коренится в таблице интернирования строк, которая имеет статус c, поэтому она будет поддерживать все литеральные строки в течение всего времени жизни процесса.

Если я изменю вашу строку на динамически создаваемую, эта таблица больше не будет иметь корня. Вместо этого, как только вы выходите из GetStructRawData, команда !gcroot сообщает, что строка больше не является корневым. Т.е. с точки зрения G C его больше нет, оно перестало существовать, оно лишено жизни, оно покоится с миром, это бывшая нить.

Я согласен с Маром c, что причина, по которой ваша программа все еще работает, просто случайна. Вы можете восстановить ссылку, и объект окажется в том же месте. Не рассчитывай на это.

...