2016-02-24 3 views
8

Если stackalloc используется с ссылочных типов, как показано нижеПочему stackalloc не может использоваться со ссылочными типами?

var arr = stackalloc string[100]; 

есть ошибка

Cannot take the address of, get the size of, or declare a pointer to a managed type ('string')

Почему так? Почему CLR не может объявить указатель на управляемый тип?

ответ

7

«Проблема» больше: на C# у вас не может быть указателя на управляемый тип. Если вы пытаетесь писать (в C#):

string *pstr; 

вы получите:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('string')

Теперь stackalloc T[num] возвращает T* (смотри, например, here), так ясно stackalloc не может быть использован с ссылочные типы.

Причина, по которой вы не можете иметь указатель на ссылочный тип, вероятно, связана с тем, что GC может свободно перемещать типы ссылок вокруг памяти (чтобы сжать память), поэтому срок действия указателя может быть коротким ,

Обратите внимание, что в C++/CLI можно прикрепить ссылочный тип и взять его адрес (см pin_ptr)

+0

У вас есть указатель на управляемый тип на C#, он просто не встроен в язык, как в C++/CLI. https://msdn.microsoft.com/en-us/library/1246yz8f(v=vs.110).aspx - Использовать GCHandleType.Pinned как второй аргумент, а затем вызвать AddrOpPinnedObject() в результате. – Nuzzolilo

+0

@Nuzzolilo Вы пробовали? Если я правильно помню, вы получите исключение, если попытаетесь «GCHandleType.Pinned» управляемый объект. – xanatos

+0

Я помню, что сделал это много лет назад, но это было так долго, что моя память могла быть неправильной: P. Я попробую еще раз и посмотрю, что произойдет. – Nuzzolilo

0

Поскольку C# работает по сбору мусора для safetiness памяти, в отличие от C++, были вы, как ожидается, знать ноу-хау управления памятью.

, например, посмотрите на следующий код:

public static void doAsync(){ 
    var arr = stackalloc string[100]; 
    arr[0] = "hi"; 
    System.Threading.ThreadPool.QueueUserWorkItem(()=>{ 
      Thread.Sleep(10000); 
      Console.Write(arr[0]); 
    }); 
} 

Программа Исли сбою. потому что arr выделен стекми, объект + его память исчезнет, ​​как только закончится doAsync. функция lamda по-прежнему указывает на этот недопустимый адрес памяти, и это недопустимое состояние.

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

Схемы является:
статическими объектами -> живет в течение всего времени applocation
локального объект -> живет до тех пор, как Scope, который создал их в действительных
кучи выделяются объекты (созданный с new) - > существует, если кто-то держит ссылку на них.

Другая проблема заключается в том, что сбор мусора работает в периоды. когда объект является локальным, он должен быть завершен, как только функция закончится, потому что после этого времени память будет переопределена другими переменными. GC не может быть принудительно завершенным объектом или не должен в любом случае.

Хорошо, однако, что C# JIT иногда (не всегда) может определить, что объект может быть надежно распределен в стеке, и прибегает к распределению стека, если это возможно (иногда, иногда).

В C++ с другой стороны, вы можете объявить все enywhere, но это приходит с меньшим safetyness то C# или Java, но вы можете подстроить вам приложение и достижение высокой производительности - низкие ресурсов приложения

+0

* «поэтому исключение памяти не может произойти с примитивами». * Это неверно. Если бы вы использовали ints, вы все равно получаете доступ к освобожденной памяти, если вы попытаетесь получить к ней доступ после освобождения массива из стека. –

+0

Массивы в C# - полные объекты fledge, поэтому здесь нет противоречия -> массив примитивов -> объект с примитивами примитивов -> та же проблема. когда я говорю «примитивы», я имею в виду такие переменные, как 'int',' bool' и т. д., а не такие типы, как массивы, которые являются объектами –

-1

Я думаю, Ксанатос опубликовал правильный ответ.

В любом случае, это не ответ, а вместо этого контрпример к другому ответу.

Рассмотрим следующий код:

using System; 
using System.Threading; 

namespace Demo 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      doAsync(); 
      Thread.Sleep(2000); 
      Console.WriteLine("Did we finish?"); // Likely this is never displayed. 
     } 

     public static unsafe void doAsync() 
     { 
      int n = 10000; 
      int* arr = stackalloc int[n]; 
       ThreadPool.QueueUserWorkItem(x => { 
       Thread.Sleep(1000); 

       for (int i = 0; i < n; ++i) 
        arr[i] = 0; 
      }); 
     } 
    } 
} 

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

Это показывает, что причина, по которой stackalloc не может использоваться со ссылочными типами, заключается не только в предотвращении такого рода ошибок.

+0

lol. но 'int []' является объектом (ссылочным типом), поэтому вы ничего не доказали. –

+0

@DavidHaim в этом коде нет объекта 'int []'. –

+0

@DavidHaim В этом коде нет 'int []'. 'arr' - это' int * '. –

2

Компилятор Just-In-Time в .NET выполняет две важные функции при преобразовании MSIL, сгенерированного компилятором C#, в исполняемый машинный код. Очевидным и видимым является генерирование машинного кода. Неочевидное и полностью невидимое задание создает таблицу, которая сообщает сборщику мусора, где искать ссылки на объекты, когда GC происходит во время выполнения метода.

Это необходимо, потому что корни объектов нельзя просто сохранить в куче GC, как поле класса, но также хранится в локальных переменных или регистры процессора. Чтобы правильно выполнить эту работу, джиттер должен знать точную структуру фрейма стека и типы хранимых там переменных, чтобы он мог правильно создать эту таблицу. Таким образом, позже сборщик мусора может выяснить, как прочитать правильное смещение фрейма стека или регистр CPU, чтобы получить значение корня объекта. Указатель на кучу GC.

Это проблема, когда вы используете stackalloc. Этот синтаксис использует функцию CLR, которая позволяет программе объявлять пользовательский тип значения. Задняя дверь вокруг обычных объявлений с управляемым типом с ограничением на то, что этот тип значения не может содержать никаких полей. Просто капля памяти, это зависит от программы для создания правильных смещений в этом блобе. Компилятор C# помогает вам создавать эти смещения на основе объявления типа и выражения индекса.

Также очень распространенная в программе C++/CLI та же функция пользовательского типа значений может предоставить хранилище для собственного объекта C++. Требуется только пространство для хранения этого объекта, правильная его инициализация и доступ к членам этого объекта C++ - это задание, которое компилятор C++ определяет. Ничто из того, что GC не должен знать.

Таким образом, основное ограничение заключается в том, что нет способа предоставить информацию о типе для этой ячейки памяти. Что касается CLR, это просто простые байты без структуры, таблица, которую использует GC, не имеет возможности описать ее внутреннюю структуру.

Неизбежно, единственный тип типа, который вы можете использовать, - это тип, который не требует ссылки на объект, о которой должен знать GC. Блестящие типы значений или указатели. Так что System.String - это не-go, это ссылочный тип. Ближе вы могли бы получить, что это «тягучий» является:

char** mem = stackalloc char*[100]; 

С дальнейшим ограничением, что это полностью зависит от вас, чтобы убедиться, что символ * элементы указывают либо возлагали или неуправляемого строки. И что вы не индексируете «массив» вне пределов. Это не очень практично.

Смежные вопросы