Alokacja pamięci: Stack vs Heap?


84

Jestem mylony z podstawami alokacji pamięci między stosem a stertą . Zgodnie ze standardową definicją (rzeczy, które wszyscy mówią), wszystkie typy wartości zostaną przydzielone do stosu, a typy referencyjne trafią do sterty .

Rozważmy teraz następujący przykład:

class MyClass
{
    int myInt = 0;    
    string myString = "Something";
}

class Program
{
    static void Main(string[] args)
    {
       MyClass m = new MyClass();
    }
}

Teraz, w jaki sposób alokacja pamięci nastąpi w języku C #? Czy przedmiot MyClass(to znaczy m) zostanie całkowicie przydzielony do Sterty? To znaczy int myInti string myStringobie pójdą na stos?

A może obiekt zostanie podzielony na dwie części i zostanie przydzielony do obu lokalizacji pamięci, czyli Stack i Heap?


Głosowałem w górę po prostu dlatego, że chociaż te długo utrzymywane głupie stwierdzenia są błędne, wierzę, że będzie kilka dobrych odpowiedzi. Jednak dość łatwo jest wymyślić trywialne kontrargumenty przeciwko sugerowanej „podwójnej alokacji” (wskazówka: obiekt-klasa może - i często tak się dzieje - żyć poza granicami wywołań funkcji).

Czy to odpowiada na twoje pytanie? Co i gdzie jest stos i sterta?
Olivier Rogier,

Odpowiedzi:


55

mjest alokowany na stercie i to obejmuje myInt. Sytuacje, w których typy pierwotne (i struktury) są przydzielane na stosie, występują podczas wywołania metody, która przydziela miejsce na zmienne lokalne na stosie (ponieważ jest szybsze). Na przykład:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

    void Foo(int x, int y) {
       int rv = x + y + myInt;
       myInt = 2^rv;
    }
}

rv, x, yWszystko będzie na stosie. myIntznajduje się gdzieś na stercie (i musi być dostępny przez thiswskaźnik).


7
Ważnym dodatkiem jest zapamiętanie, że „stos” i „sterta” są tak naprawdę szczegółami implementacji w .NET. Jest całkowicie możliwe utworzenie legalnej implementacji języka C #, która w ogóle nie używa alokacji opartej na stosie.
JSB

5
Zgadzam się, że należy je traktować w ten sposób, ale nie jest do końca prawdą, że są to wyłącznie szczegóły dotyczące implementacji. Jest to wyraźnie zaznaczone w publicznej dokumentacji API oraz w standardzie językowym (EMCA-334, ISO / IEC 23270: 2006) (tj. „Wartości struktur są przechowywane„ na stosie ”. Ostrożni programiści mogą czasami zwiększyć wydajność poprzez rozsądne użycie struktur. ") Ale tak, jeśli szybkość alokacji sterty stanowi wąskie gardło dla twojej aplikacji, prawdopodobnie robisz to źle (lub używasz niewłaściwego języka).
Błoto

65

Powinieneś rozważyć pytanie, gdzieJako szczegół implementacji przydzielania obiektów. Nie ma dla Ciebie znaczenia, gdzie dokładnie są przechowywane bity obiektu. Może mieć znaczenie, czy obiekt jest typem referencyjnym, czy typem wartości, ale nie musisz martwić się o to, gdzie będzie przechowywany, dopóki nie zaczniesz musieć optymalizować zachowania czyszczenia pamięci.

Podczas gdy typy odwołań są zawsze przydzielane na stercie w bieżących implementacjach, typy wartości mogą być przydzielane na stosie - ale niekoniecznie. Typ wartości jest przydzielany na stosie tylko wtedy, gdy jest to zmienna lokalna lub tymczasowa bez zmiany znaczenia, która nie jest zawarta w typie referencyjnym i nie jest przydzielona w rejestrze.

  • Jeśli typ wartości jest częścią klasy (jak w naszym przykładzie), trafi na stos.
  • Jeśli jest w pudełku, skończy na stercie.
  • Jeśli jest w tablicy, skończy na stercie.
  • Jeśli jest to zmienna statyczna, wyląduje na stercie.
  • Jeśli zostanie przechwycony przez zamknięcie, wyląduje na stercie.
  • Jeśli jest używany w iteratorze lub bloku asynchronicznym, trafi na stos.
  • Jeśli jest utworzony przez niebezpieczny lub niezarządzany kod, może zostać przydzielony w dowolnym typie struktury danych (niekoniecznie w stosie lub stercie).

Czy jest coś, co przegapiłem?

Oczywiście byłbym niedbały, gdybym nie linkował do postów Erica Lipperta na ten temat:


1
Ed: Dokładnie, kiedy to ma znaczenie?
Gabe,

1
@ Gabe: Nie ma znaczenia, gdzie przechowywane są bity. Np. Jeśli debugujesz zrzut awaryjny, nie zajdziesz zbyt daleko, chyba że wiesz, gdzie szukać obiektów / danych.
Brian Rasmussen,

14
Sytuacje, które przegapiłeś, to: jeśli typ wartości pochodzi z niezarządzanego kodu, do którego uzyskano dostęp za pośrednictwem niebezpiecznego wskaźnika, prawdopodobnie nie znajduje się on ani na stosie, ani na zarządzanej stercie. Może znajdować się na niezarządzanej stercie lub w jakiejś strukturze danych, która nawet nie jest stertą. Cała idea, że ​​istnieje „kupa”, jest również mitem. Stosy mogą być dziesiątki. Ponadto, jeśli jitter zdecyduje się zarejestrować wartość, to nie ma jej na stosie lub stercie, ale znajduje się w rejestrze.
Eric Lippert,

1
Część druga Erica Lipperta była fantastyczną lekturą, dziękuję za link!
Dan Bechard

2
Ma to znaczenie, ponieważ jest zadawane w wywiadach, ale nie w prawdziwym życiu. :)
Mayank

23

„Wszystkie typy VALUE zostaną przydzielone do stosu” jest bardzo, bardzo złe; zmienne strukturalne mogą żyć na stosie jako zmienne metod. Jednak pola typu żyją z tym typem . Jeśli deklarowanym typem pola jest klasa, wartości znajdują się na stercie jako część tego obiektu. Jeśli typ deklarujący pola jest strukturą, pola są częścią tej struktury, gdziekolwiek ta struktura znajduje.

Nawet zmienne metod mogą znajdować się na stercie, jeśli są przechwytywane (metoda lambda / anon) lub stanowią część (na przykład) bloku iteratora.


1
I nie zapomnij o pudełkach: jeśli masz object x = 12;w metodzie, liczba 12 zostanie zapisana na stercie, nawet jeśli jest to liczba całkowita (typ wartości).
Gabe

@ Gabe: Miejsca przechowywania typu wartości zawierają w sobie pola (publiczne i prywatne) typu wartości. Lokalizacje pamięci typu referencyjnego albo przechowują null, albo odwołują się do obiektu sterty odpowiedniego typu. Dla każdego typu wartości istnieje odpowiadający mu typ obiektu sterty; próba zapisania typu wartości w miejscu przechowywania typu referencyjnego spowoduje utworzenie nowego obiektu odpowiadającego mu typu obiektu sterty, skopiowanie wszystkich pól do tego nowego obiektu i zapisanie odniesienia do obiektu w lokalizacji przechowywania typu referencyjnego. C # udaje typ wartość i typ obiektu są takie same, ale ...
SuperCat

... taki punkt widzenia wprowadza zamieszanie, a nie zrozumienie. Unboxed, List<T>.Enumeratorktóry jest przechowywany w zmiennej tego typu, będzie wykazywał semantykę wartości, ponieważ jest to typ wartości. Jednak, List<T>.Enumeratorktóry jest przechowywany w zmiennej typu IEnumerator<T>, będzie zachowywał się jak typ referencyjny. Jeśli ktoś uważa ten drugi za inny typ niż pierwszy, różnicę w zachowaniu można łatwo wyjaśnić. Udawanie, że są tego samego typu, znacznie utrudnia rozumowanie.
supercat

12

2

Stos

To stackblok pamięci do przechowywania local variablesi parameters. Stos logicznie rośnie i kurczy się w miarę wprowadzania i zamykania funkcji.

Rozważ następującą metodę:

public static int Factorial (int x)
{
    if (x == 0) 
    {
        return 1;
    }

    return x * Factorial (x - 1);
}

Ta metoda jest rekurencyjna, co oznacza, że ​​wywołuje samą siebie. Za każdym razem, gdy metoda jest wprowadzana, na stosie przydzielana jest nowa wartość int , a za każdym razem, gdy metoda kończy działanie, przydział int jest zwalniany .


Sterta

  • Sterta to blok pamięci, w którym objects(tj. reference-type instances) Rezydują. Za każdym razem, gdy tworzony jest nowy obiekt, jest on alokowany na stercie i zwracane jest odwołanie do tego obiektu. Podczas wykonywania programu sterta zaczyna się zapełniać w miarę tworzenia nowych obiektów. Środowisko wykonawcze ma moduł odśmiecania pamięci, który okresowo zwalnia obiekty ze stosu, więc program nie działa Out Of Memory. Obiekt kwalifikuje się do cofnięcia przydziału, gdy tylko nie odwołuje się do niego nic, co samo w sobiealive .
  • Sterta również przechowuje static fields. W przeciwieństwie do obiektów przeznaczonych na stercie (które można dostać śmieci zebrane) these live until the application domain is torn down.

Rozważ następującą metodę:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder ref1 = new StringBuilder ("object1");
        Console.WriteLine (ref1);
        // The StringBuilder referenced by ref1 is now eligible for GC.

        StringBuilder ref2 = new StringBuilder ("object2");
        StringBuilder ref3 = ref2;
        // The StringBuilder referenced by ref2 is NOT yet eligible for GC.
        Console.WriteLine (ref3); // object2
    }
}    

W powyższym przykładzie zaczynamy od utworzenia obiektu StringBuilder, do którego odwołuje się zmienna ref1, a następnie wypisujemy jego zawartość. Ten obiekt StringBuilder jest następnie natychmiast kwalifikowany do wyrzucania elementów bezużytecznych, ponieważ nic go później nie używa. Następnie tworzymy kolejny obiekt StringBuilder, do którego odwołuje się zmienna ref2, i kopiujemy to odwołanie do ref3. Mimo że ref2 nie jest używany po tym punkcie, ref3 utrzymuje ten sam obiekt StringBuilder przy życiu - zapewniając, że nie kwalifikuje się on do kolekcji, dopóki nie skończymy używać ref3.

Instancje typu wartości (i odwołania do obiektów) znajdują się wszędzie tam, gdzie zadeklarowano zmienną. Jeśli instancja została zadeklarowana jako pole w ramach typu klasy lub jako element tablicy, to instancja ta znajduje się na stercie.


1

proste środki

Typ wartości można ustawić na STOSIE, jest to szczegół implementacyjny, który można przypisać do jakiejś futurystycznej struktury danych.

więc lepiej jest zrozumieć, jak działa wartość i typ referencyjny, typ wartości zostanie skopiowany przez wartość, co oznacza, że ​​gdy przekażesz typ wartości jako parametr do FUNKCJI, niż zostanie on skopiowany przez naturę, oznacza to, że będziesz mieć całkowicie nową kopię .

Typy referencyjne są przekazywane przez referencje (ponownie nie rozważaj, że referencja będzie przechowywać adres ponownie w niektórych przyszłych wersjach, może być przechowywana w innych strukturach danych).

więc w twoim przypadku

myInt jest wartością typu int, która jest ekapsulowana w klasie, która wykracza poza typ referencyjny, więc zostanie powiązana z instancją klasy, która będzie przechowywana w „THE HEAP”.

sugerowałbym, możesz zacząć czytać blogi pisane przez ERIC LIPPERTS.

Blog Erica


1

Za każdym razem, gdy tworzony jest w nim obiekt, trafia do obszaru pamięci zwanego stertą. Zmienne pierwotne, takie jak int i double, są przydzielane na stosie, jeśli są lokalnymi zmiennymi metodami, oraz w stercie, jeśli są zmiennymi składowymi. W metodach zmienne lokalne są umieszczane na stosie, gdy wywoływana jest metoda, a wskaźnik stosu jest zmniejszany po zakończeniu wywołania metody. W aplikacji wielowątkowej każdy wątek będzie miał swój własny stos, ale będzie współdzielił ten sam stos. Dlatego w kodzie należy zachować ostrożność, aby uniknąć problemów z równoczesnym dostępem do przestrzeni sterty. Stos jest bezpieczny wątkowo (każdy wątek będzie miał swój własny stos), ale sterta nie jest bezpieczna wątkowo, chyba że jest chroniona przez synchronizację za pośrednictwem kodu.

Ten link jest również przydatny http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/


0

m jest odniesieniem do obiektu MyClass, więc m jest przechowywany na stosie głównego wątku, ale obiekt MyClass jest przechowywany na stercie. Dlatego myInt i myString są przechowywane w stercie. Zauważ, że m jest tylko odniesieniem (adresem do pamięci) i znajduje się na głównym stosie. kiedy m zostanie zwolniony, GC wyczyści obiekt MyClass ze sterty Aby uzyskać więcej informacji, przeczytaj wszystkie cztery części tego artykułu https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net- część-i /

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.