Использует ли " new " в структуре, выделяет его в куче или стеке?

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

8 ответов


хорошо, посмотрим, смогу ли я сделать это более ясным.

во-первых, Эш прав: вопрос не о том, где тип значения переменные есть. Это другой вопрос - и тот, на который ответ не просто "в стеке". Это более сложно (и еще сложнее в C# 2). У меня есть статьи и расширит его, если потребуется, но давайте разберемся только с new оператор.

во-вторых, все это действительно зависит от того, какого уровня вы говорите. Я смотрю на то, что компилятор делает с исходным кодом, с точки зрения IL, который он создает. Более чем возможно, что компилятор JIT будет делать умные вещи с точки зрения оптимизации довольно много "логического" распределения.

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

наконец, все это только с текущей реализацией. Спецификация C# не указывает многого из этого - это фактически деталь реализации. Есть те, кто считает, что разработчики управляемого кода действительно не должны беспокоиться. Я не уверен, что зашел бы так далеко, но стоит представить себе мир, где на самом деле все локальные переменные живут в куче, которая все равно будет соответствовать спецификации.


есть две разные ситуации с new оператор для типов значений: вы можете вызвать конструктор без параметров (например,new Guid()) или конструктор parameterful (например,new Guid(someString)). Они генерируют значительно разные IL. Чтобы понять, почему, вам нужно сравнить спецификации C# и CLI: согласно C#, все типы значений имеют конструктор без параметров. По данным Икс, спецификаций, нет типы значений имеют конструкторы без параметров. (Принесите конструкторы типа значения с отражением некоторое время - вы не найдете parameterless один.)

для C# имеет смысл рассматривать "инициализировать значение с нулями" как конструктор, потому что он поддерживает согласованность языка - вы можете подумать о new(...) as всегда вызов конструктора. Для CLI имеет смысл думать об этом по - другому, поскольку нет реального кода для вызова-и, конечно, нет кода для конкретного типа.

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

Guid localVariable = new Guid(someString);

отличается от IL, используемого для:

myInstanceOrStaticVariable = new Guid(someString);

кроме того, если значение используется в качестве промежуточного значения, например, аргумент вызова метода, вещи немного отличаются снова. Чтобы показать все эти различия, вот короткая тестовая программа. Он не показывает разницу между статическими переменными и переменными экземпляра: IL будет отличаться между stfld и stsfld, но это все.

using System;

public class Test
{
    static Guid field;

    static void Main() {}
    static void MethodTakingGuid(Guid guid) {}


    static void ParameterisedCtorAssignToField()
    {
        field = new Guid("");
    }

    static void ParameterisedCtorAssignToLocal()
    {
        Guid local = new Guid("");
        // Force the value to be used
        local.ToString();
    }

    static void ParameterisedCtorCallMethod()
    {
        MethodTakingGuid(new Guid(""));
    }

    static void ParameterlessCtorAssignToField()
    {
        field = new Guid();
    }

    static void ParameterlessCtorAssignToLocal()
    {
        Guid local = new Guid();
        // Force the value to be used
        local.ToString();
    }

    static void ParameterlessCtorCallMethod()
    {
        MethodTakingGuid(new Guid());
    }
}

вот IL для класса, исключая не относящиеся к делу биты (например, nops):

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object    
{
    // Removed Test's constructor, Main, and MethodTakingGuid.

    .method private hidebysig static void ParameterisedCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
        L_0010: ret     
    }

    .method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
    {
        .maxstack 2
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid    
        L_0003: ldstr ""    
        L_0008: call instance void [mscorlib]System.Guid::.ctor(string)    
        // Removed ToString() call
        L_001c: ret
    }

    .method private hidebysig static void ParameterisedCtorCallMethod() cil  managed    
    {   
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0011: ret     
    }

    .method private hidebysig static void ParameterlessCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
        L_0006: initobj [mscorlib]System.Guid
        L_000c: ret 
    }

    .method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        // Removed ToString() call
        L_0017: ret 
    }

    .method private hidebysig static void ParameterlessCtorCallMethod() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        L_0009: ldloc.0 
        L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0010: ret 
    }

    .field private static valuetype [mscorlib]System.Guid field
}

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

  • newobj: выделяет значение в стеке, вызывает параметризованный конструктор. Используется для промежуточных значений, например для присвоения полю или использования в качестве аргумента метода.
  • call instance: использует уже выделенное место хранения (в стеке или нет). Это используется в коде выше для присвоения локальной переменной. Если одной и той же локальной переменной присваивается значение несколько раз, используя несколько new вызовы, он просто инициализирует данные поверх старого значения-it не выделяйте больше пространства стека каждый раз.
  • initobj: использует уже выделенное место хранения и просто стирает данные. Это используется для всех наших вызовов конструктора без параметров, включая те, которые назначаются локальной переменной. Для вызова метода intermediate локальная переменная эффективно вводится, и ее значение стирается initobj.

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

void HowManyStackAllocations()
{
    Guid guid = new Guid();
    // [...] Use guid
    guid = new Guid(someBytes);
    // [...] Use guid
    guid = new Guid(someString);
    // [...] Use guid
}

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

EDIT: просто чтобы быть ясным, это верно только в некоторых случаях... в частности, значение guid не будет видно, если Guid конструктор создает исключение, поэтому компилятор C# может повторно использовать тот же слот стека. См. Эрика Липперта сообщение в блоге о значении тип конструкции для более подробной информации и случай, когда он не применить.

я многому научился в написании этого ответа - пожалуйста, попросите разъяснений, если что-то из этого неясно!


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

Если структура выделена в куче, то вызов оператора new фактически не является необходимо выделить память. Единственной целью было бы установить значения полей в соответствии с тем, что находится в конструкторе. Если конструктор не вызывается, то все поля получат значения по умолчанию (0 или null).

аналогично для структур, выделенных в стеке, за исключением того, что C# требует, чтобы все локальные переменные были установлены на некоторое значение перед их использованием, поэтому вам нужно вызвать либо пользовательский конструктор, либо конструктор по умолчанию (конструктор, который не принимает никаких параметров всегда доступен для структур).


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

Если это переменная-член, она хранится непосредственно в том, в чем она определена, если это локальная переменная или параметр, он хранится в стеке.

сравните это с классами, которые имеют ссылку везде, где структура была бы сохранена полностью, в то время как опорные точки где-то на куче. (Член внутри, локальный / параметр в стеке)

Это может помочь немного заглянуть в C++, где нет реального различия между class / struct. (В языке есть похожие имена, но они относятся только к доступности по умолчанию вещей) при вызове new вы получаете указатель на местоположение кучи, в то время как если у вас есть ссылка без указателя, она хранится непосредственно в стеке или в другом объекте, ALA structs в C#.


как и во всех типах значений, структуры всегда идут туда, где они были объявил.

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

Edit: я туманно ответил, что они всегда идут в стек. Это неправильно.


возможно, я что-то пропустил, но почему мы заботимся о распределении?

типы значений передаются по значению ;) и, таким образом, не могут быть изменены в другой области, чем там, где они определены. Чтобы изменить значение, необходимо добавить ключевое слово [ref].

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

есть, конечно, неизменяемые ссылочные типы строк, которые являются наиболее популярными.

массив макет / инициализация: Типы значений - > нулевая память [имя, zip][имя, zip] Ссылочные типы - > нулевая память - > null [ref][ref]


A class или struct объявление похоже на схему, которая используется для создания экземпляров или объектов во время выполнения. Если вы определяете class или struct вызывается Person, Person-это имя типа. Если вы объявляете и инициализируете переменную p типа Person, p считается объектом или экземпляром Person. Можно создать несколько экземпляров одного и того же типа Person, и каждый экземпляр может иметь разные значения в своем properties и fields.

A class является ссылочным типом. Когда объект class создается, переменная, которой присваивается объект, содержит только ссылку на эту память. Когда ссылка на объект присваивается новой переменной, новая переменная ссылается на исходный объект. Изменения, внесенные с помощью одной переменной, отражаются в другой переменной, поскольку оба они относятся к одним и тем же данным.

A struct тип значения. Когда struct создается переменная, для которой struct присваивается содержит фактические данные структуры. Когда struct присваивается новой переменной, она копируется. Таким образом, новая переменная и исходная переменная содержат две отдельные копии одних и тех же данных. Изменения, внесенные в одну копию, не влияют на другую копию.

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

дополнительные...


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


структуры выделяются в стек. Вот полезное объяснение:

структуры

кроме того, классы при создании экземпляра в .NET выделяют память на зарезервированное пространство памяти кучи или .NET. Тогда как структуры дают больше эффективность при создании экземпляра из-за выделения в стеке. Кроме того, следует отметить, что передача параметров внутри структур делаются так по стоимости.