Pytanie Co i gdzie są stosy i sterty?


Programowanie książek językowych wyjaśnia, że ​​typy wartości są tworzone na stos, a typy odniesienia są tworzone na sterta, bez wyjaśnienia, czym są te dwie rzeczy. Nie przeczytałem jednoznacznego wyjaśnienia tego. Rozumiem co stos jest. Ale,

  • gdzie i czym one są (fizycznie w pamięci prawdziwego komputera)?
  • W jakim stopniu są kontrolowane przez środowisko uruchomieniowe systemu operacyjnego lub języka?
  • Jaki jest ich zakres?
  • Co decyduje o wielkości każdego z nich?
  • Co sprawia, że ​​jeden jest szybszy?

7126
2017-09-17 04:18


pochodzenie


naprawdę dobre wyjaśnienie można znaleźć tutaj Jaka jest różnica między stosem a stertą? - Songo
Również (naprawdę) dobrze: codeproject.com/Articles/76153/... (część stosu / sterty) - Ben
Można znaleźć dobre wyjaśnienie tutaj - Bharat
youtube.com/watch?v=clOUdVDDzIM&spfreload=5 - Selvamani
Powiązane, patrz Stack Clash. Stosowanie remediacji Stack Clash wpłynęło na niektóre aspekty zmiennych systemowych i zachowań, takich jak rlimit_stack. Zobacz także Red Hat Numer 1463241 - jww


Odpowiedzi:


Stos to pamięć ustawiona jako przestrzeń do rysowania dla wątku wykonania. Kiedy funkcja jest wywoływana, blok jest zarezerwowany na szczycie stosu dla zmiennych lokalnych i niektórych danych księgowych. Gdy funkcja ta powraca, blok staje się nieużywany i można go użyć przy następnym wywołaniu funkcji. Stos jest zawsze rezerwowany w kolejności LIFO (ostatni w pierwszym wyjściu); ostatni zarezerwowany blok to zawsze następny blok do uwolnienia. To naprawdę ułatwia śledzenie stosu; zwolnienie bloku ze stosu to nic innego jak dostosowanie jednego wskaźnika.

Sterta jest pamięcią zarezerwowaną dla alokacji dynamicznej. W przeciwieństwie do stosu, nie ma wymuszonego wzorca przydziału i dealokacji bloków ze sterty; możesz przydzielić blok w dowolnym momencie i zwolnić go w dowolnym momencie. To sprawia, że ​​o wiele bardziej skomplikowane jest śledzenie, które części sterty są przydzielane lub wolne w danym momencie; istnieje wiele niestandardowych podzielników sterty dostępnych do strojenia wydajności sterty dla różnych wzorców użytkowania.

Każdy wątek dostaje stos, podczas gdy zwykle jest tylko jedna stertka dla aplikacji (chociaż często zdarza się, że wiele stert dla różnych rodzajów przydziału).

Aby odpowiedzieć bezpośrednio na pytania:

W jakim stopniu są kontrolowane przez środowisko uruchomieniowe systemu operacyjnego lub języka?

System operacyjny przydziela stos dla każdego wątku na poziomie systemu podczas tworzenia wątku. Zazwyczaj system operacyjny jest wywoływany przez środowisko wykonawcze języków w celu przydzielania sterty dla aplikacji.

Jaki jest ich zakres?

Stos jest dołączony do wątku, więc gdy wątek wychodzi ze stosu, jest odzyskiwany. Sterta jest zwykle przydzielana podczas uruchamiania aplikacji przez środowisko wykonawcze i jest odzyskiwana, gdy aplikacja (proces technologiczny) zostanie zakończona.

Co decyduje o wielkości każdego z nich? 

Rozmiar stosu jest ustalany podczas tworzenia wątku. Rozmiar sterty ustawiany jest przy uruchamianiu aplikacji, ale może rosnąć, gdy potrzebne jest miejsce (alokator żąda większej ilości pamięci z systemu operacyjnego).

Co sprawia, że ​​jeden jest szybszy?

Stos jest szybszy, ponieważ wzorzec dostępu sprawia, że ​​przydzielanie i zwalnianie z niego pamięci jest banalnie proste (wskaźnik / liczba całkowita jest po prostu zwiększana lub zmniejszana), podczas gdy stert ma znacznie bardziej złożone prowadzenie księgowości związane z alokacją lub dealokacją. Ponadto każdy bajt w stosie jest często bardzo często wykorzystywany, co oznacza, że ​​jest on mapowany do pamięci podręcznej procesora, dzięki czemu jest bardzo szybki. Innym hitem wydajności sterty jest to, że sterty, będące w większości zasobami globalnymi, zwykle muszą być wielowątkowo bezpieczne, tj. Każda alokacja i dealokacja muszą być - zazwyczaj - zsynchronizowane z "wszystkimi" dostępem do innych sterty w programie.

Wyraźna demonstracja:
Źródło obrazu: vikashazrati.wordpress.com


5239
2017-09-17 04:52



Dobra odpowiedź - ale myślę, że powinieneś dodać, że podczas gdy stos jest przydzielany przez system operacyjny podczas uruchamiania procesu (zakładając istnienie systemu operacyjnego), jest on utrzymywany w linii przez program. Jest to kolejny powód, dla którego stos jest szybszy - operacje push i pop są zazwyczaj jedną instrukcją maszyny, a nowoczesne maszyny mogą wykonywać co najmniej 3 z nich w jednym cyklu, podczas gdy przydzielanie lub zwalnianie sterty wymaga wywoływania kodu OS. - sqykly
Naprawdę jestem zdezorientowany przez diagram na końcu. Myślałem, że mam to, dopóki nie zobaczyłem tego obrazu. - Sina Madani
@Anarelle procesor uruchamia instrukcje z lub bez os. Przykładem bliskim mojemu sercu jest SNES, który nie miał żadnych wywołań API, żadnego systemu operacyjnego, jaki znamy dzisiaj - ale miał stos. Alokacja na stosie jest dodawaniem i odejmowaniem w tych systemach i to jest w porządku dla zmiennych zniszczonych, gdy są wychwytywane przez powrót z funkcji, która je stworzyła, ale jest to konstytutywne dla, powiedzmy, konstruktora, którego wyniku nie można po prostu Wyrzucony. Do tego potrzebujemy sterty, która nie jest przywiązana do wywołania i powrotu. Większość systemów operacyjnych ma interfejsy API, nie ma powodu, aby robić to samodzielnie - sqykly
"stack to pamięć odłożona jako przestrzeń scratch". Chłodny. Ale gdzie właściwie jest "odkładany" pod względem struktury pamięci Java? Czy jest to Heap memory / Non-stap memory / Other (struktura pamięci Java wg betsol.com/2017/06/... ) - Jatin Shashoo


Stos:

  • Przechowywany w pamięci RAM komputera tak jak sterty.
  • Zmienne utworzone na stosie wykroczą poza zakres i zostaną automatycznie zwolnione.
  • Znacznie szybciej przydzielić w porównaniu do zmiennych na stercie.
  • Wdrożone z rzeczywistą strukturą danych stosu.
  • Przechowuje dane lokalne, adresy zwrotne, używane do przekazywania parametrów.
  • Może mieć przepełnienie stosu, gdy używana jest zbyt duża część stosu (głównie z nieskończonej lub zbyt głębokiej rekursji, bardzo duże przydziały).
  • Dane utworzone na stosie mogą być używane bez wskaźników.
  • Możesz użyć tego stosu, jeśli dokładnie wiesz, ile danych musisz przydzielić przed kompilacją i nie jest zbyt duży.
  • Zwykle ma już ustalony maksymalny rozmiar podczas uruchamiania programu.

Sterta:

  • Przechowywany w pamięci RAM komputera, podobnie jak stos.
  • W C ++ zmienne na stercie muszą być niszczone ręcznie i nigdy nie mogą wykraczać poza zakres. Dane są usuwane za pomocą delete, delete[], lub free.
  • Wolniej przydzielać w porównaniu do zmiennych na stosie.
  • Używane na żądanie w celu przydzielenia bloku danych do użycia przez program.
  • Może mieć fragmentację, gdy jest dużo alokacji i dezalokacji.
  • W C ++ lub C dane tworzone na stercie będą wskazywane przez wskaźniki i przydzielane przez new lub malloc odpowiednio.
  • Może mieć awarie alokacji, jeśli zażądano przydzielenia zbyt dużego bufora.
  • Użytkownik użyłby sterty, jeśli nie wiesz dokładnie, ile danych potrzebujesz w czasie wykonywania lub jeśli potrzebujesz przydzielić dużo danych.
  • Odpowiedzialny za wycieki pamięci.

Przykład:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

2095
2017-09-17 04:20



Wskaźnik pBuffer i wartość b znajdują się na stosie i są najczęściej przydzielane przy wejściu do funkcji. W zależności od kompilatora bufor może być przydzielony również przy wejściu do funkcji. - Andy
Jest to powszechne nieporozumienie, że C język zdefiniowany przez C99 standard językowy (dostępny pod adresem open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), wymaga "stosu". W rzeczywistości słowo "stos" nie pojawia się nawet w standardzie. Odpowiada to na zdania w / na CZużycie stosu jest generalnie prawdziwe, ale w żaden sposób nie jest wymagane przez ten język. Widzieć knosof.co.uk/cbook/cbook.html aby uzyskać więcej informacji, w szczególności w jaki sposób C jest implementowany na architekturach nieparzystych, takich jak en.wikipedia.org/wiki/Burroughs_large_systems - johne
@Brian Powinieneś wyjaśnić czemu buffer [] i wskaźnik pBuffer są tworzone na stosie i dlaczego dane pBuffer są tworzone na stercie. Myślę, że niektóre osoby mogą być zdezorientowane przez twoją odpowiedź, ponieważ mogą uważać, że program wyraźnie instruuje, że pamięć jest przydzielona na stos zamiast sterty, ale tak nie jest. Czy to dlatego, że Buffer jest typem wartości, podczas gdy pBuffer jest typem referencyjnym? - Howiecamp
@Remover: Żaden wskaźnik nie ma adresu i może wskazywać coś na stosie lub na stosie równo. new, malloc i kilka innych funkcji podobnych do malloc przypisanych do sterty i zwrócą adres przydzielonej pamięci. Dlaczego chcesz przydzielić na stertę? Aby twoja pamięć nie zniknęła z pola widzenia i została zwolniona, dopóki tego nie chcesz. - Brian R. Bondy
"Odpowiedzialny za wycieki pamięci" - hałdy nie są odpowiedzialne za wycieki pamięci! Lazy / Forgetful / ex-java programistów / programistów, którzy nie dają bzdur! - Laz


Najważniejsze jest to, że sterty i stosy są ogólnymi terminami na sposoby alokowania pamięci. Mogą być wdrażane na wiele różnych sposobów, a terminy odnoszą się do podstawowych pojęć.

  • W stosie przedmiotów przedmioty układają się jeden na drugim w kolejności, w jakiej zostały tam umieszczone, i można usunąć tylko górny (bez obalania całego przedmiotu).

    Stack like a stack of papers

    Prostota stosu polega na tym, że nie trzeba utrzymywać tabeli zawierającej zapis każdej sekcji przydzielonej pamięci; jedyne informacje o stanie, których potrzebujesz, to pojedynczy wskaźnik na końcu stosu. Aby przydzielić i usunąć alokację, wystarczy zwiększyć lub zmniejszyć ten pojedynczy wskaźnik. Uwaga: stos można czasami zaimplementować, aby rozpocząć od początku sekcji pamięci i rozciągać się w dół, a nie w górę.

  • W kupie nie ma określonego porządku na drodze do umieszczania przedmiotów. Możesz sięgać i usuwać przedmioty w dowolnej kolejności, ponieważ nie ma wyraźnego "górnego" przedmiotu.

    Heap like a heap of licorice allsorts

    Alokacja sterty wymaga zachowania pełnej informacji o tym, jaka pamięć jest przydzielana, a co nie, a także niektórych czynności konserwacyjnych, aby zmniejszyć fragmentację, znaleźć przyległe segmenty pamięci wystarczająco duże, aby pasowały do ​​żądanego rozmiaru, i tak dalej. Pamięć może zostać zwolniona w dowolnym momencie, pozostawiając wolną przestrzeń. Czasami alokator pamięci wykona zadania konserwacyjne, takie jak defragmentacja pamięci przez przeniesienie przydzielonej pamięci lub zbieranie śmieci - identyfikowanie w czasie wykonywania, gdy pamięć nie jest już w zakresie i zwalnianie jej.

Obrazy te powinny dość dobrze opisać dwa sposoby przydzielania i zwalniania pamięci w stosie i kupie. Mniam!

  • W jakim stopniu są kontrolowane przez środowisko uruchomieniowe systemu operacyjnego lub języka?

    Jak wspomniano, sterty i stosy są terminami ogólnymi i mogą być implementowane na wiele sposobów. Programy komputerowe zazwyczaj mają stos nazywany a stos połączeń która przechowuje informacje istotne dla bieżącej funkcji, takie jak wskaźnik do dowolnej funkcji, z której została wywołana, oraz wszelkie zmienne lokalne. Ponieważ funkcje wywołują inne funkcje, a następnie zwracają, stos rośnie i kurczy się, aby przechowywać informacje z funkcji znajdujących się dalej w stosie wywołań. Program tak naprawdę nie ma nad nim kontroli w czasie wykonywania; zależy to od języka programowania, systemu operacyjnego, a nawet od architektury systemu.

    Sterta jest ogólnym terminem używanym dla każdej pamięci przydzielanej dynamicznie i losowo; tj. nieczynne. Pamięć jest zwykle przydzielana przez system operacyjny, a funkcje API wywołujące aplikacje umożliwiają taką alokację. W zarządzaniu dynamicznie przydzielaną pamięcią, która jest zwykle obsługiwana przez system operacyjny, potrzeba sporo wysiłku.

  • Jaki jest ich zakres?

    Stos wywołań jest tak niskopoziomową koncepcją, że nie odnosi się do "zakresu" w sensie programowania. Jeśli zdemontujesz jakiś kod, zobaczysz relatywne odniesienia w stylu wskaźnika do części stosu, ale jeśli chodzi o język wyższego poziomu, język narzuca własne reguły zakresu. Ważnym aspektem stosu jest jednak to, że po powrocie funkcji wszystko, co lokalne do tej funkcji, jest natychmiast zwalniane ze stosu. Działa to w sposób, w jaki oczekuje się, że zadziała, biorąc pod uwagę działanie języków programowania. W stosie jest to również trudne do zdefiniowania. Zakres jest czymkolwiek, co jest narażone przez system operacyjny, ale twój język programowania prawdopodobnie dodaje swoje reguły dotyczące tego, co "zakres" jest w twojej aplikacji. Architektura procesora i system operacyjny używają adresowania wirtualnego, które procesor tłumaczy na adresy fizyczne i występują błędy stron itp. Śledzą one, które strony należą do których aplikacji. Jednak nigdy nie musisz się tym martwić, ponieważ używasz dowolnej metody, której używa twój język programowania do przydzielania i zwalniania pamięci oraz sprawdzania błędów (jeśli alokacja / zwolnienie nie powiedzie się z jakiegokolwiek powodu).

  • Co decyduje o wielkości każdego z nich?

    Znowu zależy to od języka, kompilatora, systemu operacyjnego i architektury. Stos zwykle jest wstępnie przydzielany, ponieważ z definicji musi być ciągłą pamięcią (więcej o tym w ostatnim akapicie). Kompilator języka lub system operacyjny określają jego rozmiar. Nie przechowujesz ogromnych ilości danych na stosie, więc będzie wystarczająco duży, aby nigdy nie był w pełni wykorzystywany, z wyjątkiem przypadków niechcianej nieskończonej rekurencji (stąd "przepełnienia stosu") lub innych nietypowych decyzji programistycznych.

    Sterty to ogólne określenie wszystkiego, co można dynamicznie alokować. W zależności od tego, w jaki sposób na to patrzysz, ciągle zmienia rozmiar. W nowoczesnych procesorach i systemach operacyjnych dokładny sposób działania jest w każdym razie bardzo abstrakcyjny, więc zwykle nie musisz się martwić o to, jak to działa w głąb, z wyjątkiem tego (w językach, w których ci to pozwala) nie możesz używać pamięci jeszcze nie przydzielono Ci pamięci ani pamięci, którą uwolniłeś.

  • Co sprawia, że ​​jeden jest szybszy?

    Stos jest szybszy, ponieważ cała wolna pamięć jest zawsze ciągła. Nie ma potrzeby utrzymywania listy wszystkich segmentów wolnej pamięci, tylko jednego wskaźnika na bieżący wierzchołek stosu. Kompilatory zazwyczaj przechowują ten wskaźnik w specjalnym, szybkim tempie zarejestrować w tym celu. Co więcej, kolejne operacje na stosie są zwykle skoncentrowane w bardzo bliskich obszarach pamięci, które na bardzo niskim poziomie są dobre do optymalizacji przez procesor w pamięci podręcznej.


1261
2018-03-19 14:38



David Nie zgadzam się, że jest to dobry obraz, lub że "układ push-down" to dobry termin do zilustrowania tej koncepcji. Kiedy dodajesz coś do stosu, druga zawartość stosu nie są pchnięte w dół, pozostają tam, gdzie są. - thomasrutter
Ta odpowiedź zawiera wielki błąd. Zmienne statyczne nie są przydzielane na stosie. Zobacz moją odpowiedź [link] stackoverflow.com/a/13326916/1763801 dla wyjaśnienia. Ty identyfikujesz "automatyczne" zmienne ze zmiennymi "statycznymi", ale one wcale nie są takie same - davec
Mówiąc konkretnie, mówisz "statycznie przydzielone zmienne lokalne" są przydzielane na stosie. W rzeczywistości są one przydzielane w segmencie danych. Tylko automatycznie przydzielone zmienne (które zawierają większość, ale nie wszystkie zmienne lokalne, a także rzeczy takie jak parametry funkcji przekazywane przez wartość zamiast przez odniesienie) są przydzielane na stosie. - davec
Właśnie sobie uświadomiłem, że masz rację - w C, przydzielenie statyczne jest odrębną rzeczą, a nie terminem na wszystko, czego nie ma dynamiczny. Edytowałem swoją odpowiedź, dziękuję. - thomasrutter
To nie tylko C. Java, Pascal, Python i wiele innych mają pojęcia alokacji statycznej versus automatycznej i alokacji dynamicznej. Mówienie "alokacja statyczna" oznacza to samo wszędzie. W żadnym języku alokacja statyczna nie oznacza "dynamiczny". Chcesz określenie "automatyczna" alokacja dla tego, co opisujesz (tj. Rzeczy na stosie). - davec


(Przeniosłem tę odpowiedź z innego pytania, które było mniej więcej podróbką tego.)

Odpowiedź na twoje pytanie dotyczy konkretnej implementacji i może się różnić w zależności od kompilatorów i architektur procesorów. Jednak tutaj jest uproszczone wyjaśnienie.

  • Zarówno stos, jak i sterty są obszarami pamięci przydzielonymi z podstawowego systemu operacyjnego (często wirtualnej pamięci, która jest mapowana na pamięć fizyczną na żądanie).
  • W środowisku wielowątkowym każdy wątek będzie posiadał własny, całkowicie niezależny stos, ale udostępni tę stertę. Współbieżny dostęp musi być kontrolowany na stercie i nie jest możliwy na stosie.

Sterty

  • Sterta zawiera połączoną listę używanych i wolnych bloków. Nowe przydziały na stropie (wg new lub malloc) są spełnione przez utworzenie odpowiedniego bloku z jednego z wolnych bloków. Wymaga to aktualizacji listy bloków na stercie. To meta informacje o blokach na stercie jest również przechowywany na stercie, często na małym obszarze, tuż przed każdym blokiem.
  • Wraz ze wzrostem stosu nowe bloki są często przydzielane od niższych adresów w kierunku wyższych adresów. W ten sposób można pomyśleć o kupie jako sterta bloków pamięci, które rosną w miarę przydzielania pamięci. Jeśli stertę jest zbyt mała do alokacji, rozmiar często można zwiększyć, zdobywając więcej pamięci z podstawowego systemu operacyjnego.
  • Przydzielanie i zwalnianie wielu małych bloków może opuścić stertę w stanie, w którym jest wiele małych wolnych bloków przeplatanych między używanymi blokami. Żądanie przydzielenia dużego bloku może się nie powieść, ponieważ żaden z wolnych bloków nie jest wystarczająco duży, aby spełnić żądanie alokacji, nawet jeśli łączny rozmiar wolnych bloków może być wystarczająco duży. To się nazywa fragmentacja sterty.
  • Kiedy użyty blok, który sąsiaduje z wolnym blokiem, zostaje zwolniony, nowy wolny blok może zostać połączony z sąsiednim wolnym blokiem, aby utworzyć większy wolny blok, skutecznie zmniejszając fragmentację sterty.

The heap

Stos

  • Stos często działa w bliskim tandemie ze specjalnym rejestrem na procesorze o nazwie wskaźnik stosu. Początkowo wskaźnik stosu wskazuje na wierzchołek stosu (najwyższy adres na stosie).
  • Procesor ma specjalne instrukcje dla popychanie wartości na stos i popping ich z powrotem ze stosu. Każdy Pchać przechowuje wartość w bieżącej lokalizacji wskaźnika stosu i zmniejsza wskaźnik stosu. ZA Muzyka pop pobiera wartość wskazywaną przez wskaźnik stosu, a następnie zwiększa wskaźnik stosu (nie należy mylić tego faktu dodawanie wartość do stosu maleje wskaźnik stosu i usuwanie wartość wzrasta to. Pamiętaj, że stos rośnie do dołu). Zapisane i pobrane wartości są wartościami rejestrów procesora.
  • Kiedy funkcja jest wywoływana, procesor używa specjalnych instrukcji, które popychają prąd wskaźnik instrukcji, tj. adres kodu wykonującego na stosie. CPU następnie przeskakuje do funkcji, ustawiając wskaźnik instrukcji do adresu funkcji o nazwie. Później, gdy funkcja zwraca, stary wskaźnik instrukcji jest wyskakiwany ze stosu, a wykonywanie wznawiane jest po kodzie zaraz po wywołaniu funkcji.
  • Po wprowadzeniu funkcji wskaźnik stosu zostaje zmniejszony, aby przydzielić więcej miejsca na stosie dla zmiennych lokalnych (automatycznych). Jeśli funkcja ma jedną lokalną 32-bitową zmienną, cztery bajty są umieszczane na stosie. Gdy funkcja zwraca, wskaźnik stosu zostaje cofnięty, aby zwolnić przydzielony obszar.
  • Jeśli funkcja ma parametry, są one wypychane na stos przed wywołaniem funkcji. Kod w funkcji jest wtedy w stanie nawigować w górę stosu od bieżącego wskaźnika stosu, aby zlokalizować te wartości.
  • Zagnieżdżanie wywołań funkcji działa jak urok. Każde nowe wywołanie przydzieli parametry funkcji, adres zwrotny i miejsce na zmienne lokalne i te zapisy aktywacyjne może być zestawiony dla połączeń zagnieżdżonych i będzie się rozwijać w prawidłowy sposób, gdy funkcje zostaną zwrócone.
  • Ponieważ stos jest ograniczonym blokiem pamięci, możesz spowodować przepełnienie stosu przez wywołanie zbyt wielu funkcji zagnieżdżonych i / lub przydzielenie zbyt dużej ilości miejsca dla zmiennych lokalnych. Często obszar pamięci używany dla stosu jest skonfigurowany w taki sposób, że zapisywanie poniżej najniższego (najniższego adresu) stosu wywoła pułapkę lub wyjątek w CPU. Ten wyjątkowy warunek może zostać przechwycony przez środowisko wykonawcze i przekształcony w wyjątek przepełnienia stosu.

The stack

Czy funkcja może być przydzielona na stercie zamiast stosu?

Nie, rekordy aktywacji dla funkcji (tj. Zmiennych lokalnych lub automatycznych) są przydzielane na stosie, który jest używany nie tylko do przechowywania tych zmiennych, ale także do śledzenia zagnieżdżonych wywołań funkcji.

Sposób zarządzania stertą zależy od środowiska uruchomieniowego. C używa malloc i używa C ++ new, ale wiele innych języków ma kolekcję śmieci.

Jednak stos jest bardziej niskopoziomową cechą ściśle związaną z architekturą procesora. Rosnące sterty, gdy nie ma wystarczająco dużo miejsca, nie jest zbyt trudne, ponieważ może być zaimplementowane w wywołaniu biblioteki, które obsługuje stertę. Jednakże, wzrost stosu jest często niemożliwy, ponieważ przepełnienie stosu jest odkrywane tylko wtedy, gdy jest za późno; a wyłączenie wątku wykonania jest jedyną realną opcją.


664
2017-07-31 15:54



@Martin - Bardzo dobra odpowiedź / wyjaśnienie niż bardziej abstrakcyjna przyjęta odpowiedź. Przykładowy program montażowy pokazujący wskaźniki / rejestry stosu podczas wywoływania funkcji vis byłby bardziej ilustracyjny. - Bikal Lem
Każdy typ referencyjny jest składem typów wartości (int, string itp.). Jak już wspomniano, te typy wartości są przechowywane w stosie, niż to, jak działa, gdy są one częścią typu odniesienia. - Nps
Ta odpowiedź była, moim zdaniem, najlepsza, ponieważ pomogła mi zrozumieć, czym naprawdę jest zwrot i jak odnosi się do tego "adresu zwrotnego", z którym się od czasu do czasu spotykam, co to znaczy popchnąć funkcję na stos, i dlaczego funkcje są wypychane na stosy. Świetna odpowiedź! - Alex
Jest to najlepsze w mojej opinii, mianowicie dla wspomnienia, że ​​stos / stos są bardzo konkretna implementacja. Pozostałe odpowiedzi zakładają los rzeczy o języku i środowisku / OS. +1 - Qix
Co masz na myśli "Kod w funkcji jest wtedy w stanie nawigować w górę stosu od bieżącego wskaźnika stosu, aby zlokalizować te wartości." ? Czy możesz to rozwinąć? - Koray Tugay


W następującym kodzie C #

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Oto sposób zarządzania pamięcią

Picture of variables on the stack

Local Variables które musi trwać tak długo, jak długo wywołanie funkcji jest w stosie. Sterta jest używana dla zmiennych, których żywotność tak naprawdę nie znamy z góry, ale spodziewamy się, że potrwają jakiś czas. W większości języków bardzo ważne jest, abyśmy wiedzieli podczas kompilacji, jak duża jest zmienna, jeśli chcemy przechowywać ją na stosie.

Obiekty (które różnią się rozmiarem w miarę ich aktualizacji) idą na kupę, ponieważ nie wiemy w czasie tworzenia, jak długo będą trwać. W wielu językach stertą są śmieci zebrane w celu znalezienia obiektów (takich jak obiekt cls1), które nie mają już żadnych odniesień.

W Javie większość obiektów trafia bezpośrednio do sterty. W językach takich jak C / C ++, struktury i klasy często mogą pozostać na stosie, gdy nie masz do czynienia ze wskaźnikami.

Więcej informacji można znaleźć tutaj:

Różnica między alokacją sterty i sterty "timmurphy.org

i tu:

Tworzenie obiektów na stosie i stosie

Ten artykuł jest źródłem powyższego obrazu: Sześć ważnych pojęć .NET: stos, stertowanie, typy wartości, typy odniesienia, boks i rozpakowywanie - CodeProject

ale pamiętaj, że może zawierać pewne nieścisłości.


352
2017-11-09 12:28



To jest niepoprawne. i i cls nie są zmiennymi "statycznymi". nazywane są zmiennymi "lokalnymi" lub "automatycznymi". Jest to bardzo ważne rozróżnienie. Zobacz [link] stackoverflow.com/a/13326916/1763801 dla wyjaśnienia - davec
Nie powiedziałem, że są statyczne zmienne. Powiedziałem, że int i cls1 są statyczne przedmiotów. Ich pamięć jest przydzielana statycznie, a więc trafiają na stos. Jest to przeciwieństwo obiektu, który wymaga dynamicznej alokacji pamięci, która w związku z tym idzie na stertę. - Snowcrash
Cytuję "Statyczne przedmioty ... idź na stos". To po prostu źle. Statyczne przedmioty idą w segmencie danych, automatyczne przedmioty idą na stos. - davec
Również ten, kto napisał ten artykuł w codeproject, nie wie, o czym mówi. Na przykład mówi, że "prymitywne wymagają pamięci typu statycznego", co jest całkowicie nieprawdziwe. Nic nie powstrzyma cię od dynamicznego przydzielania prymitywów w stercie, po prostu napisz coś w stylu "int array [] = new int [num]" i voila, prymitywy przydzielone dynamicznie w .NET. To tylko jedna z kilku nieścisłości. - davec
Zmodyfikowałem twój post, ponieważ popełniłeś poważne techniczne błędy dotyczące tego, co dzieje się w stosie i kupie. - Tom Leys


Stos Kiedy wywołujesz funkcję, argumenty tej funkcji i inne dodatkowe narzucają się na stos. Niektóre informacje (np. Gdzie wrócić) są tam również przechowywane. Kiedy deklarujesz zmienną w swojej funkcji, ta zmienna jest również przydzielana na stosie.

Zwolnienie ze stosu jest dość proste, ponieważ zawsze zwalniasz w odwrotnej kolejności, w której przydzielasz. Podczas wchodzenia do funkcji dodawane są elementy stosu, odpowiednie dane są usuwane podczas ich zamykania. Oznacza to, że zazwyczaj pozostajesz w małym obszarze stosu, chyba że wywołasz wiele funkcji, które wywołują wiele innych funkcji (lub tworzą rekursywne rozwiązanie).

Kupa Sterty to ogólna nazwa miejsca, w którym umieszczasz dane, które tworzysz w locie. Jeśli nie wiesz, ile statków kosmicznych ma utworzyć twój program, prawdopodobnie użyjesz nowego operatora (lub malloc lub równoważnego) do stworzenia każdego statku kosmicznego. Ta alokacja będzie trwać przez jakiś czas, więc najprawdopodobniej uwolnimy rzeczy w innej kolejności, niż je stworzyliśmy.

Tak więc hałda jest o wiele bardziej złożona, ponieważ w końcu są obszary pamięci, które są nieużywane przeplatane fragmentami, które są - pamięć jest pofragmentowana. Znalezienie wolnej pamięci o wymaganym rozmiarze jest trudnym problemem. Dlatego należy unikać kupy (choć wciąż jest ona często używana).

Realizacja Implementacja zarówno stosu, jak i sterty jest zwykle w dół do środowiska wykonawczego / systemu operacyjnego. Często gry i inne aplikacje, które mają krytyczne działanie, tworzą własne rozwiązania pamięciowe, które pobierają dużą porcję pamięci ze sterty, a następnie rozdają ją wewnętrznie, aby uniknąć polegania na systemie operacyjnym dla pamięci.

Jest to tylko praktyczne, jeśli twoje użycie pamięci jest inne niż normalne - to jest dla gier, w których ładujesz poziom w jednej ogromnej operacji i możesz odrzucić cały pakiet w kolejnej ogromnej operacji.

Fizyczna lokalizacja w pamięci Jest to mniej istotne niż myślisz z powodu technologii o nazwie Pamięć wirtualna co sprawia, że ​​twój program uważa, że ​​masz dostęp do pewnego adresu, gdzie dane fizyczne są gdzie indziej (nawet na twardym dysku!). Adresy, które dostajesz na stos, rosną w kolejności, gdy drzewo połączeń jest coraz głębsze. Adresy sterty są nieprzewidywalne (tj. Specyficzne dla implementacji) i szczerze mówiąc nie są ważne.


191
2017-09-17 04:27



Zalecenie, aby unikać używania sterty jest dość silne. Nowoczesne systemy mają dobrych menedżerów sterty, a nowoczesne języki dynamiczne intensywnie wykorzystują stertę (nie martwiąc się o programistę). Powiedziałbym, że używaj sterty, ale z ręcznym przydziałem, nie zapomnij uwolnić! - Greg Hewgill
Jeśli możesz użyć stosu lub sterty, użyj stosu. Jeśli nie możesz użyć stosu, naprawdę nie ma wyboru. Używam obu bardzo dużo i oczywiście przy użyciu std :: vector lub podobnych trafień sterty. Dla początkującego unikasz kupy, ponieważ stos jest po prostu taki łatwy !! - Tom Leys
Jeśli twój język nie implementuje usuwania śmieci, inteligentne wskaźniki (oddzielone alokowane obiekty, które owijają się wokół wskaźnika, który odwołuje się do liczenia dynamicznie przydzielonych części pamięci) są ściśle związane ze zbieraniem pamięci i są przyzwoitym sposobem zarządzania stertą w bezpiecznym miejscu i bez wycieków. Są one implementowane w różnych frameworkach, ale nie są również trudne do wdrożenia dla własnych programów. - BenPen
"Dlatego należy unikać kupy (choć wciąż jest ona często używana)." Nie jestem pewien, co to właściwie oznacza, zwłaszcza że pamięć jest zarządzana inaczej w wielu językach wysokiego poziomu. Ponieważ to pytanie jest oznaczone jako agnostyk językowy, powiedziałbym, że ten konkretny komentarz / wiersz jest źle umieszczony i nie dotyczy. - JonnoHampson
Dobry punkt @ JonnoHampson - Podczas gdy tworzymy prawidłowy punkt, argumentowałbym, że jeśli pracujesz w "wysokim poziomie językowym" z GC prawdopodobnie nie przejmujesz się mechanizmami alokacji pamięci w ogóle - i tak nie nawet dbać o to, jaki jest stos i kupa. - Tom Leys


W celu wyjaśnienia, ta odpowiedź ma nieprawidłowe informacje (Tomasz poprawiono odpowiedź po komentarzach, fajnie :)). Inne odpowiedzi pozwalają uniknąć wyjaśnienia, co oznacza alokacja statyczna. Wyjaśnię więc trzy główne formy alokacji i jak zwykle odnoszą się do stosu, stosu i segmentu danych poniżej. Pokażę także kilka przykładów w C / C ++ i Pythonie, aby pomóc ludziom zrozumieć.

Zmienne "statyczne" (alokowane statycznie) nie są przydzielane na stosie. Nie zakładajcie tego - wiele osób robi tylko dlatego, że "statyczne" dźwięki przypominają "stos". W rzeczywistości nie istnieją ani w stosie, ani w stosie. Są częścią tego, co się nazywa segment danych.

Jednak ogólnie lepiej jest rozważyć "zakres" i "dożywotni"zamiast" stosu "i" sterty ".

Zakres odnosi się do tego, jakie części kodu mogą uzyskać dostęp do zmiennej. Ogólnie myślimy zasięg lokalny (można uzyskać do niej dostęp tylko przy użyciu bieżącej funkcji) Zakres globalny (można uzyskać do niego dostęp w dowolnym miejscu), chociaż zakres może być o wiele bardziej złożony.

Czas życia odnosi się do sytuacji, gdy zmienna jest przydzielana i zwalniana podczas wykonywania programu. Zwykle myślimy o tym przydzielenie statyczne (zmienna będzie trwała przez cały czas trwania programu, dzięki czemu będzie przydatna do przechowywania tych samych informacji dla wielu wywołań funkcji) automatyczne przydzielanie (zmienna zachowuje się tylko podczas pojedynczego wywołania funkcji, dzięki czemu jest przydatna do przechowywania informacji, która jest używana tylko podczas twojej funkcji i może zostać odrzucona po zakończeniu) alokacja dynamiczna (zmienne, których czas trwania jest definiowany w czasie wykonywania, zamiast czasu kompilacji, jak statyczny lub automatyczny).

Chociaż większość kompilatorów i interpretatorów implementuje to zachowanie w podobny sposób, jeśli chodzi o używanie stosów, hałd itp., Kompilator może czasami łamać te konwencje, jeśli chce, dopóki zachowanie jest poprawne. Na przykład ze względu na optymalizację zmienna lokalna może istnieć tylko w rejestrze lub zostać całkowicie usunięta, nawet jeśli większość zmiennych lokalnych istnieje w stosie. Jak już zauważono w kilku komentarzach, możesz zaimplementować kompilator, który nie używa nawet stosu lub sterty, a zamiast tego inne mechanizmy przechowywania (rzadko, ponieważ stosy i sterty są do tego świetne).

Przekażę prosty, opatrzony adnotacją kod C, aby zilustrować to wszystko. Najlepszym sposobem nauki jest uruchomienie programu pod debuggerem i obserwowanie zachowania. Jeśli wolisz czytać pythona, przejdź na koniec odpowiedzi :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Szczególnie przejmującym przykładem tego, dlaczego ważne jest rozróżnienie między czasem życia a zakresem, jest to, że zmienna może mieć zasięg lokalny, ale statyczny czas życia - na przykład "someLocalStaticVariable" w powyższym przykładzie kodu. Takie zmienne mogą sprawić, że nasze wspólne, ale nieformalne nawyki nazewnictwa będą bardzo mylące. Na przykład, kiedy mówimy "lokalny"zwykle mamy na myśli"lokalnie określana automatycznie zmienna alokowana"a kiedy mówimy globalnie, zwykle mamy na myśli"globalnie określona statycznie zmienna alokowana"Niestety, jeśli chodzi o rzeczy takie jak"Zmienne przydzielone statycznie w zakresie pliku"wielu ludzi właśnie mówi ..."huh ???".

Niektóre opcje składni w C / C ++ zaostrzają ten problem - na przykład wiele osób uważa, że ​​zmienne globalne nie są "statyczne" ze względu na składnię pokazaną poniżej.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Zauważ, że umieszczenie słowa kluczowego "static" w powyższej deklaracji uniemożliwia var2 posiadanie globalnego zasięgu. Niemniej jednak globalna zmienna 1 ma przydzielenie statyczne. To nie jest intuicyjne! Z tego powodu staram się nigdy nie używać słowa "static" przy opisie zakresu, a zamiast tego mówić coś w rodzaju "file" lub "file limited". Jednak wiele osób używa wyrażenia "static" lub "static scope", aby opisać zmienną, do której można uzyskać dostęp tylko z jednego pliku kodu. W kontekście życia, "statyczne" zawsze oznacza, że ​​zmienna jest przydzielana przy starcie programu i zwalniana po zakończeniu programu.

Niektórzy myślą o tych koncepcjach jako specyficznych dla C / C ++. Oni nie są. Na przykład poniższa próbka Pythona ilustruje wszystkie trzy typy przydziałów (istnieją pewne subtelne różnice w językach interpretowanych, których tutaj nie dostanę).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

169
2017-09-17 04:48



Chciałbym odnieść się do zmiennej statycznej zadeklarowanej w funkcji jako posiadającej tylko lokalną dostępność, ale generalnie nie używałby z nim terminu "zakres". Warto również zauważyć, że jeden stos / stos, w którym języki mają zasadniczo zerową elastyczność: język, który zapisuje kontekst wykonania na stosie, nie może wykorzystywać tego samego stosu do przechowywania rzeczy, które będą musiały przeżyć konteksty, w których są tworzone. . Niektóre języki, takie jak PostScript mają wiele stosów, ale mają "stertę", która zachowuje się bardziej jak stos. - supercat
@supercat To wszystko ma sens. Zdefiniowałem zakres jako "jakie części kodu mogą dostęp zmienna "(i uważam, że jest to najbardziej standardowa definicja), więc myślę, że się zgadzamy :) - davec
Uznałbym "zakres" a zmienna jako ograniczone czasem i przestrzenią. Zmienna w zakresie klasy obiekt jest wymagana do przechowywania jej wartości, o ile obiekt istnieje. Zmienna w zasięgu wykonywalnego kontekstu jest wymagana do przechowywania jej wartości, dopóki pozostanie wykonanie w tym kontekście. Deklaracja zmiennej statycznej tworzy identyfikator którego zakres jest ograniczony do bieżącego bloku, który jest dołączony do a zmienna którego zakres jest nieograniczony. - supercat
@supercat Dlatego używam słowa lifetime, w ten sposób określam to, co nazywasz zasięgiem czasu. Zmniejsza potrzebę przeciążania słowa "zasięg" o tak wiele znaczeń. O ile wiem, wydaje się, że nie ma całkowitej zgody co do dokładnych definicji, nawet wśród źródeł kanonicznych. Moja terminologia wywodzi się częściowo z K & R i częściowo z dominującego użycia na pierwszym wydziale CS, którego uczyłem / uczyłem. Zawsze dobrze jest usłyszeć inny świadomy widok. - davec
chyba żartujesz. czy naprawdę możesz zdefiniować zmienną statyczną wewnątrz funkcji? - Zaeem Sattar


Inne bardzo dobrze odpowiedziały na ogólne ruchy, więc dodam kilka szczegółów.

  1. Stos i stos nie muszą być pojedyncze. Typową sytuacją, w której masz więcej niż jeden stos, jest więcej niż jeden wątek w procesie. W tym przypadku każdy wątek ma własny stos. Możesz także mieć więcej niż jedną stertę, na przykład niektóre konfiguracje DLL mogą powodować różne biblioteki DLL przydzielane z różnych stert, dlatego też nie jest dobrym pomysłem zwolnienie pamięci przydzielonej przez inną bibliotekę.

  2. W C można uzyskać korzyści z przydzielania zmiennej długości za pomocą alloca, który przydziela na stosie, w przeciwieństwie do przydziału, który przydziela na stercie. Ta pamięć nie przetrwa twojego polecenia return, ale jest przydatna w buforze scratch.

  3. Tworzenie ogromnego tymczasowego bufora w systemie Windows, z którego nie korzystasz zbyt wiele, nie jest bezpłatne. Dzieje się tak dlatego, że kompilator wygeneruje pętlę sondy stosu, która jest wywoływana za każdym razem, gdy wprowadzana jest twoja funkcja, aby upewnić się, że stos istnieje (ponieważ system Windows używa pojedynczej strony strażnika na końcu stosu, aby wykryć, kiedy musi rozwinąć stos. Jeśli uzyskasz dostęp do pamięci więcej niż jedną stronę poza końcem stosu, ulegniesz awarii). Przykład:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

156
2017-09-17 07:16



Re "w przeciwieństwie do przydziału": Czy masz na myśli "w przeciwieństwie do malloc"? - Peter Mortensen
Jak przenośny jest alloca? - Peter Mortensen
@PeterMortensen to nie POSIX, przenośność nie jest gwarantowana. - Don Neufeld


Inni odpowiedzieli bezpośrednio na twoje pytanie, ale kiedy próbujemy zrozumieć stos i stertę, myślę, że warto wziąć pod uwagę układ pamięci tradycyjnego procesu UNIX (bez wątków i mmap()oparte na podzielnikach). The Glosariusz zarządzania pamięcią strona internetowa zawiera schemat tego układu pamięci.

Stos i stertę tradycyjnie znajdują się na przeciwnych końcach wirtualnej przestrzeni adresowej procesu. Stos rośnie automatycznie po uzyskaniu dostępu, aż do rozmiaru ustawionego przez jądro (który można dostosować za pomocą setrlimit(RLIMIT_STACK, ...)). Sterty rosną, gdy alokator pamięci wywołuje funkcję brk() lub sbrk() wywołanie systemowe, mapowanie większej liczby stron pamięci fizycznej na wirtualną przestrzeń adresową procesu.

W systemach bez pamięci wirtualnej, takich jak niektóre systemy wbudowane, często stosuje się ten sam podstawowy układ, z wyjątkiem tego, że stos i sterty mają stały rozmiar. Jednak w innych systemach wbudowanych (takich jak te oparte na mikrokontrolerach Microchip PIC) stos programu jest oddzielnym blokiem pamięci, który nie jest adresowalny przez instrukcje przenoszenia danych, i może być modyfikowany lub czytany pośrednio przez instrukcje przepływu programu (wywołanie, powrót itp.). Inne architektury, takie jak procesory Intel Itanium, mają wiele stosów. W tym sensie stos jest elementem architektury procesora.


126
2017-09-17 04:57





Myślę, że wiele innych osób udzieliło ci w większości poprawnych odpowiedzi na ten temat.

Jednak brakowało jednego szczegółu, że "kupa" powinna w rzeczywistości być prawdopodobnie nazywana "wolnym sklepem". Powodem tego rozróżnienia jest to, że oryginalny darmowy sklep został zaimplementowany ze strukturą danych zwaną dwumianową stertą. Z tego powodu przydział z wczesnych implementacji funkcji malloc () / free () był przydzielany ze sterty. Jednak w dzisiejszych czasach większość darmowych sklepów jest wdrażanych z bardzo rozbudowanymi strukturami danych, które nie są dwumianowymi kupkami.


108
2017-09-17 04:29



Kolejna nitpick - większość odpowiedzi (lekko) sugeruje, że użycie "stosu" jest wymagane przez C język. Jest to powszechne nieporozumienie, choć jest to (zdecydowanie) dominujący paradygmat wdrażania C99 6.2.4 automatic storage duration objects (zmienne). W rzeczywistości słowo "stos" nawet nie pojawia się w C99 standard językowy: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf - johne
[@Heath] Mam mały komentarz do twojej odpowiedzi. Spójrz na zaakceptowaną odpowiedź to pytanie. Mówi, że darmowy sklep  najprawdopodobniej jest taki sam jak sterta, choć niekoniecznie jest. - OmarOthman