Дилемма с использованием типов значений с оператором `new` в C # - PullRequest
10 голосов
/ 06 апреля 2011

Когда operator new() используется со ссылочным типом, пространство для экземпляра выделяется в куче, а сама ссылочная переменная помещается в стек. Кроме того, все в экземпляре ссылочного типа, выделенном в куче, обнуляется.
Например вот класс:

class Person
{
    public int id;
    public string name;
}

В следующем коде:

class PersonDemo
{
    static void Main()
    {
        Person p = new Person();
        Console.WriteLine("id: {0}  name: {1}", p.id, p.name);
    }
}

p переменная находится в стеке, а созданный экземпляр Person (все его члены) находится в куче. p.id будет 0, а p.name будет null. Это будет иметь место, потому что все, что выделено в куче, обнуляется.

Что меня смущает, так это то, что я использую тип значения с оператором new. Например, примите во внимание следующую структуру:

struct Date
{
    public int year;
    public int month;
    public int day;
}

class DateDemo
{
    static void Main()
    {
        Date someDate;
        someDate= new Date();

        Console.WriteLine("someDate is: {0}/{1}/{2}", 
            someDate.month, someDate.day, someDate.year);
    }
}

Теперь я хотел бы знать, что делают следующие строки из main:

        Date someDate;
        someDate= new Date();

В первой строке someDate переменная размещена в стеке. Точно 12 байтов.
Мой вопрос Что происходит во второй строке? Что делает operator new()? Он только обнуляет членов структуры Date или же он выделяет пространство в куче? С одной стороны, я не ожидал бы, что new выделит место в куче, конечно, потому что в первой строке память уже выделена в стеке для экземпляра структуры. С другой стороны, я ожидал бы, что new выделит пространство в куче и обратный адрес этого пространства, потому что это то, что должен делать new. Может быть, это потому, что я пришел из C ++ фона.

Тем не менее, если ответ таков: «когда new используется с типами значений, он только обнуляет члены объекта», то это немного противоречивое значение оператора new, потому что:

  1. при использовании new с типами значений он только обнуляет члены объекта в стеке
  2. при использовании new со ссылочными типами он выделяет память в куче для экземпляра и обнуляет его элементы

Заранее спасибо,
Приветствия

Ответы [ 5 ]

20 голосов
/ 07 апреля 2011

Сначала позвольте мне исправить ваши ошибки.

Когда оператор new () используется со ссылочным типом, пространство для экземпляра выделяется в куче, а сама ссылочная переменная помещается в стек.

Ссылка, которая является результатом "new", является значением , а не переменной . Значение относится к месту хранения.

Ссылка, конечно, возвращается в регистр процессора . Копируется ли когда-либо содержимое этого регистра ЦП в стек вызовов, решает оптимизатор джиттера. Это никогда не должно жить в стеке; он может жить вечно в регистрах, или он может быть скопирован непосредственно из реестра в управляемую кучу, или, в небезопасном коде, он может быть скопирован непосредственно в неуправляемую память.

Стек - это деталь реализации. вы не знаете , когда используется стек, если только вы не посмотрите на приведенный код.

Переменная p находится в стеке, а созданный экземпляр Person (все его члены) - в куче. p.id будет 0, а p.name будет нулевым.

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

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

Я из C ++ фона.

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

В первой строке переменная someDate размещается в стеке. Точно 12 байтов.

Давайте предположим, ради этого, что эта 12-байтовая структура размещена в стеке. Кажется разумным.

Мой вопрос: что происходит во второй строке? Что делает оператор new ()? Он только обнуляет членов структуры Date или также выделяет пространство в куче?

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

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

Правильный вывод, правдоподобные рассуждения. Выделение кучи не выполняется , поскольку компилятор знает, что ни одна часть этой операции не требует долговременного хранилища . Вот для чего куча; Когда компилятор определяет, что данная переменная может жить дольше, чем активация текущего метода, он генерирует код, который выделяет хранилище для этой переменной в долговременном хранилище «куча». Если он определяет, что переменная определенно имеет короткое время жизни, то он использует стек (или регистры) в качестве оптимизации.

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

Неправильно. «new» не гарантирует, что куча выделена. Скорее, «новый» гарантирует, что конструктор вызывается в обнуленной памяти.

Вернемся к вашему вопросу:

Он только обнуляет элементы структуры Date или также выделяет пространство в куче?

Мы знаем, что он не выделяет место в куче. Обнуляет ли это членов структуры даты?

Это сложный вопрос. В спецификации сказано, что происходит, когда вы говорите

someDate = new Date();    
  • определяется адрес someDate
  • выделено место (вне "стека") для нового объекта.Обнуляется.
  • , затем вызывается конструктор, если таковой имеется, причем "this" является ссылкой на новое хранилище стека
  • , тогда байты нового хранилища стека копируются по адресу someDate.

Теперь, это действительно то, что происходит ?Вы были бы совершенно в своем праве заметить, что невозможно сказать , выделено ли новое пространство стека, инициализировано и скопировано, или инициализировано "старое" пространство стека.

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

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

Более подробную информацию об этой проблеме и ее анализе компилятором см. В моей статье на эту тему..

http://blogs.msdn.com/b/ericlippert/archive/2010/10/11/debunking-another-myth-about-value-types.aspx

5 голосов
/ 06 апреля 2011

OK, здесь все просто:

class Program
{
    static void Main(string[] args)
    {
        DateTime dateTime = new DateTime();
        dateTime = new DateTime();
        Console.Read();
    }
}

, который компилируется в этот код IL:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  1
  .locals init ([0] valuetype [mscorlib]System.DateTime dateTime)
  IL_0000:  nop
  IL_0001:  ldloca.s   dateTime
  IL_0003:  initobj    [mscorlib]System.DateTime
  IL_0009:  ldloca.s   dateTime
  IL_000b:  initobj    [mscorlib]System.DateTime
  IL_0011:  call       int32 [mscorlib]System.Console::Read()
  IL_0016:  pop
  IL_0017:  ret
} // end of method Program::Main

Как вы можете видеть, CLR будет использовать ту же локальную переменную для храненияновый тип значения, хотя он снова запустит конструктор - , что, скорее всего, просто обнулит память .Мы не можем увидеть, что такое initobj, это реализация CLR .

Реальность такова, как объясняет Эрик Липперт здесь , такого общегоправило о типах значений, выделяемых в стеке .Это чисто до реализации CLR.

2 голосов
/ 06 апреля 2011

Конструктор по умолчанию для структуры возвращает структуру со всей обнуленной памятью.То есть new SomeStruct() - это то же самое, что и default(SomeStruct).

. Затем ваш код присваивает этой переменной структуру по умолчанию.

Это все, что вы точно знаете.

То, как компилятор решает эту задачу, полностью зависит от компиляторов.

Но если вам любопытно, что происходит за кулисами, компилятор, скорее всего, просто очистит расположение стека этогоПеременная напрямую: при условии, что эта переменная хранится в стеке.Есть много вещей, которые могут этому помешать - один из примеров - это доступ к ней анонимной функции, а именно:

Func<Person> PersonFactory()
{
  Person p = new Person();
  return () => p;
}

Здесь p должен быть сохранен в куче, чтобы иметь возможность существовать после возврата из функции и т. Д., Итак что new Person() очистит эту кучу.

В любом случае.В отличие от C / C ++, с C # неплохо бы забыть о «стеке», «куче» и т. Д. AFAIK, в спецификации языка нет ни одного из этих понятий - все они специфичны для реализации.Кто знает, что некоторые будущие реализации могут, где позволяет escape-анализ, поместить некоторые значения кучи в стек, чтобы сэкономить GC немного усилий.На самом деле лучше не принимать проектные решения, специфичные для конкретной реализации спецификации C #.

1 голос
/ 06 апреля 2011

С точки зрения разработчика, вы не знаете, где он находится. Например, экзотическое устройство с CLR, которое не имеет представления о стеке -> все, что уходит в кучу. Даже если вы рассматриваете CLR для настольного компьютера, в некоторых случаях JITer может перемещать переменные из стека в кучу.

Подробнее.

0 голосов
/ 06 апреля 2011

Об обнулении структур.

Конструктор без параметров обнуляет элементы.

Если вы не используете new(), вы не сможете получить доступ к элементам структуры, если сначала не инициализируете их самостоятельно.,В противном случае вы получите «Использование возможно неназначенного поля».

...