Od jakiegoś czasu zajmuję się programowaniem, ale to głównie Java i C #. Właściwie nigdy nie musiałem samodzielnie zarządzać pamięcią. Niedawno zacząłem programować w C ++ i jestem trochę zdezorientowany, kiedy powinienem przechowywać rzeczy na stosie, a kiedy przechowywać je na stercie.
Rozumiem, że zmienne, do których dostęp jest bardzo często, powinny być przechowywane na stosie i obiektach, rzadko używane zmienne, a duże struktury danych powinny być przechowywane na stercie. Czy to prawda, czy jestem nieprawidłowy?
Odpowiedzi:
Nie, różnica między stosem a stertą nie polega na wydajności. Jest to żywotność: każda zmienna lokalna wewnątrz funkcji (wszystko, czego nie zrobisz malloc () lub new) żyje na stosie. Znika po powrocie z funkcji. Jeśli chcesz, aby coś żyło dłużej niż funkcja, która to zadeklarowała, musisz alokować to na stercie.
Aby lepiej zrozumieć, czym jest stos, podejdź do niego z drugiej strony - zamiast próbować zrozumieć, co robi stos w zakresie języka wysokiego poziomu, poszukaj „stosu wywołań” i „konwencji wywoływania” i zobacz, co maszyna naprawdę robi to, gdy wywołujesz funkcję. Pamięć komputera to tylko seria adresów; „heap” i „stack” to wynalazki kompilatora.
źródło
Powiedziałbym:
Przechowuj go na stosie, jeśli możesz.
Jeśli potrzebujesz, przechowuj go na stercie.
Dlatego preferuj stos od sterty. Oto kilka możliwych powodów, dla których nie możesz przechowywać czegoś na stosie:
Za pomocą rozsądnych kompilatorów można przydzielać na stercie obiekty o stałym rozmiarze (zwykle tablice, których rozmiar nie jest znany w czasie kompilacji).
źródło
Jest bardziej subtelny, niż sugerują inne odpowiedzi. Nie ma absolutnego podziału między danymi na stosie a danymi na stercie w zależności od tego, jak je zadeklarujesz. Na przykład:
W treści funkcji, która deklaruje
vector
(dynamiczną tablicę) dziesięciu liczb całkowitych na stosie. Ale magazyn zarządzany przez thevector
nie znajduje się na stosie.Ach, ale (inne odpowiedzi sugerują) czas życia tego magazynu jest ograniczony przez czas życia
vector
samego siebie, który tutaj jest oparty na stosie, więc nie ma znaczenia, jak jest zaimplementowany - możemy traktować go tylko jako obiekt oparty na stosie z semantyką wartości.Skąd. Załóżmy, że funkcja była:
Tak więc wszystko, co ma
swap
funkcję (i każdy złożony typ wartości powinien ją mieć) może służyć jako rodzaj możliwego do ponownego powiązania odniesienia do niektórych danych sterty w systemie, który gwarantuje jednego właściciela tych danych.Dlatego nowoczesne podejście C ++ polega na tym, aby nigdy nie przechowywać adresu danych sterty w nagich lokalnych zmiennych wskaźnikowych. Wszystkie alokacje sterty muszą być ukryte w klasach.
Jeśli to zrobisz, możesz myśleć o wszystkich zmiennych w swoim programie tak, jakby były prostymi typami wartości i całkowicie zapomnieć o sterty (z wyjątkiem pisania nowej klasy opakowującej podobnej do wartości dla niektórych danych sterty, co powinno być niezwykłe) .
Musisz tylko zachować jedną specjalną wiedzę, która pomoże Ci zoptymalizować: tam, gdzie to możliwe, zamiast przypisywać jedną zmienną do drugiej w następujący sposób:
zamień je w ten sposób:
ponieważ jest znacznie szybszy i nie generuje wyjątków. Jedynym wymaganiem jest to, że nie musisz
b
nadal utrzymywać tej samej wartości (a
zamiast tego otrzyma wartość, która zostałaby skasowanaa = b
).Wadą jest to, że takie podejście wymusza zwracanie wartości z funkcji za pośrednictwem parametrów wyjściowych zamiast rzeczywistej wartości zwracanej. Ale naprawiają to w C ++ 0x za pomocą odwołań do rvalue .
W najbardziej skomplikowanych sytuacjach możesz doprowadzić ten pomysł do skrajności i użyć inteligentnej klasy wskaźnika, takiej jak ta,
shared_ptr
która jest już w tr1. (Chociaż argumentowałbym, że jeśli wydaje ci się, że tego potrzebujesz, prawdopodobnie wyszedłeś poza słodkie miejsce zastosowania Standard C ++).źródło
Możesz również przechowywać element na stercie, jeśli ma być używany poza zakresem funkcji, w której został utworzony. Jeden idiom używany z obiektami stosu nazywa się RAII - wiąże się to z użyciem obiektu opartego na stosie jako opakowania dla zasobu, gdy obiekt zostanie zniszczony, zasób zostanie wyczyszczony. Obiekty oparte na stosie są łatwiejsze do śledzenia, kiedy możesz rzucać wyjątki - nie musisz martwić się usuwaniem obiektu opartego na stosie w programie obsługi wyjątków. To dlatego surowe wskaźniki nie są normalnie używane we współczesnym C ++, można by użyć inteligentnego wskaźnika, który może być opakowaniem opartym na stosie dla surowego wskaźnika do obiektu opartego na stercie.
źródło
Aby dodać do innych odpowiedzi, może to również dotyczyć wydajności, przynajmniej trochę. Nie znaczy to, że powinieneś się tym martwić, chyba że jest to istotne dla Ciebie, ale:
Alokowanie w stercie wymaga znalezienia śledzenia bloku pamięci, co nie jest operacją o stałym czasie (i zajmuje kilka cykli i narzut). Może to działać wolniej, gdy pamięć zostanie pofragmentowana i / lub zbliżasz się do wykorzystania 100% swojej przestrzeni adresowej. Z drugiej strony alokacje stosu są operacjami działającymi w czasie stałym, w zasadzie „darmowymi”.
Inną rzeczą do rozważenia (ponownie, naprawdę ważna tylko wtedy, gdy pojawi się problem) jest to, że zazwyczaj rozmiar stosu jest stały i może być znacznie mniejszy niż rozmiar sterty. Więc jeśli przydzielasz duże obiekty lub wiele małych obiektów, prawdopodobnie będziesz chciał użyć sterty; jeśli zabraknie miejsca na stosie, środowisko wykonawcze wyrzuci tytułowy wyjątek witryny. Zwykle nie jest to wielka sprawa, ale inna rzecz do rozważenia.
źródło
Stos jest bardziej wydajny i łatwiejszy do zarządzania danymi o określonym zakresie.
Ale sterta powinna być używana do wszystkiego, co jest większe niż kilka KB (w C ++ jest to łatwe, po prostu utwórz
boost::scoped_ptr
na stosie wskaźnik do przydzielonej pamięci).Rozważmy rekurencyjny algorytm, który ciągle woła do siebie. Bardzo trudno jest ograniczyć i zgadnąć całkowite użycie stosu! Podczas gdy na stercie alokator (
malloc()
lubnew
) może wskazać brak pamięci, zwracającNULL
lubthrow
ing.Źródło : jądro Linuksa, którego stos nie jest większy niż 8 KB!
źródło
std::unique_ptr
, która powinna być preferowana w stosunku do dowolnej biblioteki zewnętrznej, takiej jak Boost (chociaż z czasem doprowadza to do standardu).Dla kompletności możesz przeczytać artykuł Miro Samka o problemach związanych z używaniem sterty w kontekście oprogramowania wbudowanego .
Kupa problemów
źródło
Wybór, czy alokować na stercie, czy na stosie, jest dokonywany za Ciebie, w zależności od sposobu przydzielenia zmiennej. Jeśli przydzielasz coś dynamicznie, używając „nowego” wywołania, alokujesz ze sterty. Jeśli przydzielisz coś jako zmienną globalną lub jako parametr w funkcji, jest to przydzielane na stosie.
źródło
Moim zdaniem decydują dwa czynniki
W większości przypadków wolałbym używać stosu, ale jeśli potrzebujesz dostępu do zmiennej poza zakresem, możesz użyć sterty.
Aby zwiększyć wydajność podczas korzystania ze stert, można również użyć funkcji tworzenia bloku sterty, co może pomóc w zwiększeniu wydajności zamiast przydzielania każdej zmiennej w innej lokalizacji pamięci.
źródło
prawdopodobnie odpowiedź na to pytanie jest całkiem dobra. Chciałbym skierować Cię do poniższej serii artykułów, aby lepiej zrozumieć szczegóły niskiego poziomu. Alex Darby ma serię artykułów, w których przeprowadza cię przez debuger. Oto część 3 o stosie. http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/
źródło