Czy użycie" new " na strukturze przydziela ją na stercie lub stosie?

Kiedy tworzysz instancję klasy z operatorem new, pamięć zostanie przydzielona na stercie. Kiedy tworzysz instancję struktury za pomocą operatora new, gdzie przydzielana jest pamięć, na stercie lub na stosie ?

Author: ryeguy, 2008-10-15

8 answers

Zobaczmy, czy mogę to wyjaśnić.

Po pierwsze, Ash ma rację: pytanie brzmi a nie o to, gdzie przydzielane są zmienne typu value . To jest inne pytanie - i takie, na które odpowiedź nie jest tylko "na stosie". To jest bardziej skomplikowane niż to (i jeszcze bardziej skomplikowane przez C # 2). Mam artykuł na ten temat i na życzenie go rozbuduję, ale zajmijmy się tylko operatorem new.

Po drugie, wszystkie to naprawdę zależy od tego, o jakim poziomie mówisz. Patrzę na to, co kompilator robi z kodem źródłowym, w kategoriach IL, które tworzy. Jest bardziej niż możliwe, że kompilator JIT zrobi sprytne rzeczy w zakresie optymalizacji dala dość dużo" logicznego " alokacji.

Po Trzecie, ignoruję leki generyczne, głównie dlatego, że właściwie nie znam odpowiedzi, a częściowo dlatego, że to zbytnio skomplikowałoby sprawę.

Wreszcie, wszystko to jest tylko z prądem wdrożenie. C# spec nie precyzuje tego zbyt wiele - jest to efektywny szczegół implementacji. Są tacy, którzy uważają, że deweloperzy zarządzanego kodu naprawdę nie powinni się tym przejmować. Nie jestem pewien, czy posunąłbym się tak daleko, ale warto wyobrazić sobie świat, w którym w rzeczywistości wszystkie zmienne lokalne żyją na stercie - która nadal byłaby zgodna ze specyfikacją.


Istnieją dwie różne sytuacje z operatorem new na typach wartości: możesz wywołać konstruktor bez parametru (np. new Guid()) lub konstruktor parametryczny (np. new Guid(someString)). Generują one znacznie różne IL. Aby zrozumieć dlaczego, musisz porównać specyfikacje C# i CLI: zgodnie z C#, Wszystkie typy wartości mają konstruktor bez parametru. Zgodnie ze specyfikacją CLI, żadne typy wartości nie mają konstruktorów bez parametru. (Pobierz konstruktory typu value z refleksją - nie znajdziesz bez parametru.)

Sensowne jest, aby C# traktował "initialize a value with zeroes" jako konstruktor, ponieważ utrzymuje spójny język - możesz myśleć o new(...) jako zawsze wywołując konstruktor. To ma sens dla CLI myśleć o tym inaczej, ponieważ nie ma prawdziwego kodu do wywołania - a na pewno nie ma kodu specyficznego dla typu.

Robi również różnicę, co zamierzasz zrobić z wartością po jej zainicjowaniu. IL używane dla

Guid localVariable = new Guid(someString);

Różni się od IL używanego do:

myInstanceOrStaticVariable = new Guid(someString);

Dodatkowo, jeśli wartość jest używana jako wartość pośrednia, np. argument do wywołania metody, znów jest trochę inaczej. Aby pokazać wszystkie te różnice, oto krótki program testowy. Nie pokazuje różnicy między zmiennymi statycznymi a zmiennymi instancji: IL różniłby się między stfld i stsfld, ale to wszystko.

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());
    }
}

Oto IL dla klasy, z wyłączeniem nieistotnych bitów (takich jak 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
}

Jak widać, istnieje wiele różnych instrukcji używanych do wywołania konstruktor:

  • newobj: przydziela wartość na stosie, wywołuje parametryzowany konstruktor. Używany dla wartości pośrednich, np. do przypisania do pola lub jako argument metody.
  • call instance: używa już przydzielonej lokalizacji pamięci masowej(czy to na stosie, czy nie). Jest to użyte w powyższym kodzie do przypisania zmiennej lokalnej. Jeśli tej samej zmiennej lokalnej zostanie przypisana wartość kilka razy za pomocą kilku wywołań new, to po prostu inicjalizuje dane na górze Stara wartość-to nie przydziela więcej miejsca na stosie za każdym razem.
  • initobj: używa już przydzielonej lokalizacji pamięci masowej i po prostu usuwa dane. Jest to używane dla wszystkich naszych bez parametru wywołań konstruktora, łącznie z tymi, które przypisują do zmiennej lokalnej. W wywołaniu metody efektywnie wprowadzana jest zmienna lokalna, a jej wartość wymazywana jest przez initobj.

Mam nadzieję, że to pokazuje, jak skomplikowany jest temat, jednocześnie świecąc na niego trochę światła. W niektórych pojęciowych zmysłach, każde wywołanie new przydziela przestrzeń na stosie - ale jak widzieliśmy, tak naprawdę nie dzieje się to nawet na poziomie IL. Chciałbym podkreślić jeden szczególny przypadek. Weź tę metodę:

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

To "logicznie" ma 4 alokacje stosu - jeden dla zmiennej i jeden dla każdego z trzech wywołań new - ale w rzeczywistości (dla tego konkretnego kodu) stos jest przydzielany tylko raz, a następnie ta sama lokalizacja pamięci jest ponownie używana.

EDIT: po prostu być jasne, jest to prawdą tylko w niektórych przypadkach... w szczególności, wartość guid nie będzie widoczna, jeśli konstruktor Guid wyrzuci wyjątek, dlatego kompilator C# może ponownie użyć tego samego miejsca stosu. Zobacz post na blogu Erica Lipperta na temat budowy typu wartości , aby uzyskać więcej szczegółów i przypadek, w którym nie ma zastosowania.

Wiele się nauczyłem pisząc tę odpowiedź - proszę o wyjaśnienie, jeśli któraś z nich jest niejasna!

 290
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-06 18:26:19

Pamięć zawierająca pola struktury może być przydzielona na stosie lub na stosie w zależności od okoliczności. Jeśli zmienna typu struct jest zmienną lokalną lub parametrem, który nie jest przechwytywany przez anonimowego delegata lub klasę iteratora, to zostanie ona przydzielona na stos. Jeśli zmienna jest częścią jakiejś klasy, wtedy zostanie przydzielona w ramach klasy na stercie.

Jeśli struktura jest przydzielona na stercie, to wywołanie nowego operatora nie jest w rzeczywistości konieczne aby przydzielić pamięć. Jedynym celem byłoby ustawienie wartości pola zgodnie z tym, co znajduje się w konstruktorze. Jeśli konstruktor nie zostanie wywołany, wtedy wszystkie pola otrzymają swoje domyślne wartości (0 lub null).

Podobnie dla struktur przydzielonych na stosie, z tym wyjątkiem, że C# wymaga, aby wszystkie zmienne lokalne były ustawione na jakąś wartość przed ich użyciem, więc musisz wywołać konstruktor niestandardowy lub domyślny (konstruktor, który nie pobiera żadnych parametrów, jest zawsze dostępny dla struktur).

 33
Author: Jeffrey L Whitledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-15 05:12:51

Mówiąc krótko, new jest błędną nazwą dla struktur, wywołanie new po prostu wywołuje konstruktor. Jedyną lokalizacją pamięci dla struktury jest lokalizacja, którą została zdefiniowana.

Jeśli jest zmienną członkowską, jest przechowywana bezpośrednio w tym, w czym jest zdefiniowana, jeśli jest zmienną lokalną lub parametrem, jest przechowywana na stosie.

Porównujemy to z klasami, które mają odniesienie wszędzie tam, gdzie struktura byłaby przechowywana w całości, podczas gdy odniesienia znajdują się gdzieś na stercie. (Member within, local / parameter on stack)

Może pomóc zajrzeć trochę do C++, gdzie nie ma prawdziwego rozróżnienia między klasą / strukturą. (Istnieją podobne nazwy w języku, ale odnoszą się tylko do domyślnej dostępności rzeczy) gdy wywołujesz new, dostajesz wskaźnik do lokalizacji sterty, podczas gdy jeśli masz odniesienie nie-wskaźnikowe, jest ono przechowywane bezpośrednio na stosie lub w innym obiekcie, strukturach ala w C#.

 10
Author: Guvante,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-15 08:59:23

Podobnie jak w przypadku wszystkich typów wartości, struktury zawsze idą tam, gdzie były zadeklarowane .

Zobacz to pytanie tutaj aby dowiedzieć się więcej o tym, kiedy używać struktur. A to pytanie tutaj, aby uzyskać więcej informacji na temat struktur.

Edit: źle odpowiedziałem, że oni Zawsze wchodzą do stosu. To jest niepoprawne .

 4
Author: Esteban Araya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:50

Pewnie coś mi tu umyka, ale po co nam przydział?

Typy wartości są przekazywane przez value ;) i dlatego nie mogą być mutowane w innym zakresie niż tam, gdzie są zdefiniowane. Aby móc mutować wartość należy dodać słowo kluczowe [ref].

Typy referencyjne są przekazywane przez odniesienia i mogą być mutowane.

Istnieją oczywiście niezmienne typy referencyjne, które są najbardziej popularne.

Układ tablicy/inicjalizacja: Typy wartości - > pamięć zerowa [nazwa, zip] [nazwa, zip] Typy referencyjne -> pamięć zerowa - > null [ref][ref]

 4
Author: user18579,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-01 04:07:05

Deklaracja class LUB struct jest jak schemat, który jest używany do tworzenia instancji lub obiektów w czasie wykonywania. Jeśli zdefiniujesz class LUB struct wywołaną osobę, Person jest nazwą typu. Jeśli zadeklarujesz i zainicjalizujesz zmienną P typu Person, mówi się, że p jest obiektem lub instancją Person. Można utworzyć wiele instancji tego samego typu Person, a każda instancja może mieć różne wartości w swoich properties i fields.

A class jest typem odniesienia. Gdy obiekt class jest wytworzona, zmienna, do której przypisany jest obiekt, zawiera tylko odniesienie do tej pamięci. Gdy odniesienie do obiektu jest przypisane do nowej zmiennej, Nowa zmienna odnosi się do oryginalnego obiektu. Zmiany dokonane za pomocą jednej zmiennej są odzwierciedlane w drugiej zmiennej, ponieważ obie odnoszą się do tych samych danych.

A struct jest typem wartości. Gdy tworzony jest struct, zmienna, do której przypisany jest struct, przechowuje rzeczywiste dane struktury. Gdy struct jest przypisana do nowej zmiennej, jest skopiowany. Nowa i oryginalna zmienna zawierają zatem dwie oddzielne kopie tych samych danych. Zmiany dokonane w jednej kopii nie wpływają na drugą.

Ogólnie rzecz biorąc, classes są używane do modelowania bardziej złożonych zachowań lub danych, które mają zostać zmodyfikowane po utworzeniu obiektu class. Structs najlepiej nadają się do małych struktur danych, które zawierają przede wszystkim dane, które nie mają być modyfikowane po utworzeniu struct.

Po Więcej...

 2
Author: Sujit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-08 08:08:16

W zasadzie struktury, które są uważane za typy wartości, są przydzielane na stosie, podczas gdy obiekty są przydzielane na stosie, podczas gdy odniesienie do obiektu (wskaźnik) jest przydzielane na stosie.

 1
Author: bashmohandes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-15 05:26:08

Struktury zostają przydzielone do stosu. Oto pomocne Wyjaśnienie:

Structs

DODATKOWO, klasy po utworzeniu instancji w. NET przydzielają pamięć na sterty lub zarezerwowanej przestrzeni pamięci. NET. Natomiast struktury dają więcej wydajność po utworzeniu instancji z powodu alokacji na stosie. Ponadto należy zauważyć, że przekazywanie parametrów w strukturach czynią to według wartości.

 1
Author: DaveK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-29 09:12:45