Cześć ;) Czy decyzja o umieszczeniu danych na stosie lub stercie powinna zależeć od jakichś konkretnych czynników czy jest to wybór programisty? Już kilka razy natknąłem się na tekst gdzie pisało że w naprawdę zaawansowanych aplikacjach wręcz zabrania się dynamicznie alokować pamięć dlatego chciałbym poznać wasze zdanie na ten temat.
A jeśli już wskaźniki to wszystkie inteligentne?
Stos: krótki czas dostępu, mniejszy rozmiar, lokalny zakres zmiennych, brak konieczności zarządzania pamięcią
Sterta: długi czas dostępu, "globalny" zakres zmiennych, konieczność ręcznego zarządzania zasobami (ewentualnie RAII), fragmentacja pamięci
Jeżeli zamierzasz korzystać tylko ze wskaźników inteligentnych to przesiądź się na C#. Używając wskaźników inteligentnych można mieć wycieki pamięci, a kod wygląda okropnie.
@Satirev podstawowe różnice pomiędzy stosem i sterta to ja znam ;) Chodziło mi o bardziej "praktyczne" porównanie ;)
Ale nawet z tego prostego porównania wynika że w większości przypadków lepszym rozwiązaniem jest definiowanie obiektów / zmiennych w klasach na stosie a korzystając z wielu bibliotek widzę że te mimo wszystko jakby "promują" dynamiczne alokowanie danych.
Więc jeszcze raz - czym powinienem się kierować decydując się na stos / stertę? No i właśnie jak to jest z tymi inteligentnymi wskaźnikami, trzeba uważać żeby nie przesadzić z ich ilością? Czy jeżeli już używam wskaźnika to powinienem użyć inteligentnego czy to też zależy od sytuacji?
Pomijając sytuacje wyjątkowe (potwierdzone wynikami z profilera), powinieneś wszędzie trzymać się zasady zero: http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html
Inaczej mówiąc, jeśli tylko operujesz na danych, nie przejmując za nie odpowiedzialności, przekazywanie nagiego wskaźnika jest ok. W przeciwnym wypadku używaj inteligentnych wskaźników, w końcu RAII to jedna z największych zalet C++.
Jeśli chodzi o trzymanie danych na stosie/stercie, to domyślnie staram się wszystko pakować na stos, pamiętając jednak o:
- tym, że stos ma ograniczoną wielkość.
- tym, że dane ze stosu znikną¹ gdy wyjdę z obecnej funkcji, co jest istotne dla kodu bez jasnej hierarchii wywołań
¹ nawet jeśli mówimy o POD, które najprawdopodobniej zostaną nieruszone, zgodnie ze standardem odnoszenie się do takiej pamięci to UB.
- Jeżeli zależy Ci na wydajności alokacji - stos
- Jeżeli alokowane dane są na tyle duże, że mogą spowodować przepełnienie stosu - sterta.
- Jeżeli chcesz zmienić domyślną kolejność inicjalizacji pól w klasie - sterta (w zasadzie chodzi o operator new - nie ma znaczenia czy fizycznie wylądują na stosie czy na stercie).
- Jeżeli chcesz przekazać dane poza zakres w którym były tworzone (np. chcesz zwrócić duży obiekt z funkcji) - sterta
No i właśnie jak to jest z tymi inteligentnymi wskaźnikami, trzeba uważać żeby nie przesadzić z ich ilością
Inteligentne wskaźniki ze zliczaniem referencji (np. std::shared_ptr) mogą potencjalnie powodować problemy z wydajnością jeżeli są nadużywane:
- Inteligentne wskaźniki alokują dodatkowe miejsce dla licznika referencji na stercie (czyli dodatkowy new)
- Licznik referencji jest inkrementowany i dekrementowany atomowo co powoduję synchronizacje cache'ow na procesorze (dosyć czasochłonna operacja).
A jakie są różnice między stosem a stertą?
- głównie wygoda i bezpieczeństwo używania: http://msdn.microsoft.com/en-us/library/hh438473.aspx
Stos jest bezpieczniejszy, ale można go symulować przez RAII i inteligentne wskaźniki.
Jak kontrolować czy obiekt może być alokowany dynamicznie?
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Requiring_or_Prohibiting_Heap-based_Objects
Czy w zaawansowanych aplikacjach unika się alokacji na stercie?
Ktoś kogo teraz nie pomnę w magazynie "Programista" opisał jakiś czas temu ciekawą strategię - może jest na tym forum.
Zamiast alokowania:
class MojaKlasa {
int kolor;
double zapach;
};
typedef std::vector<MojaKlasa> MojVector;
lub nawet
typedef std::vector<MojaKlasa *> MojVector;
lepiej jest zrobic:
class MojVector {
std::vector<int> kolory;
std::vector<double> zapachy;
// funkcje dostepowe
};
Ogólnie chodzi o to żeby zamiast alokować tysiące malutkich obiekcików lepiej jest alokować wektory ich właściwości (cache).
Czy w zaawansowanych aplikacjach unika się alokacji na stercie?
Sterta jest niedeterministyczna, i po wielu dużych alokacjach pojawia się problem z fragmentacją.
Dlatego unika się alokacji w zastosowaniach krytycznych, gdzie wywalenie aplikacji skutkuje śmiercią albo katastrofą.
to pozwolę sobie skorzystać z okazji i zapytać. tablica z 30 ścieżkami do plików, które ustalam w trakcie działania programu.
obecnie alokuje na stertę - a może lepiej opłaca mi się alokować na stos tj. tablica[30][4096]; ?
nie ma się co zastanawiać.
string tablica[30];
jeśli na pewno będzie ich zawsze 30, albo
vector<string> tablica;
jeśli jednak nie 30.
Azarien napisał(a):
string tablica[30];
jeśli na pewno będzie ich zawsze 30
Albo
std::array<std::string, 30> array;
chociaż to może nie być takie fajne, jak się wydaje. Takiej tablicy nie powinno się przekazywać (będzie kopiowana!), powinno się przekazywać iteratory - iteratory to C++ way. ;-)
Jeżeli chcesz przekazać dane poza zakres w którym były tworzone (np. chcesz zwrócić duży obiekt z funkcji) - sterta
Rozumiem że w tym wypadku sterta po to aby uniknąć dodatkowego kopiowania przy zwracaniu obiektu zadeklarowanego w funkcji na stosie?
I jeszcze jedno pytanie a mianowicie jeśli zmienne / inne obiekty zawierane w głównej klasie zadeklaruje na stosie a główną klasę na stercie to dane wylądują na stercie? I odwrotnie - w klasie zmienne wywołuje z operatorem new a ta klasę później w innej klasie już wywołuje na stosie i gdzie wtedy lądują dane?
Według mnie najłatwiej byłoby wszystkie zmienne i obiekty których w danej klasie używam deklarować na stosie
class Utility
{
private:
std::string property;
My_class custom;
// ....
};
problem zasięgu w danej klasie znika, gdy potrzebuje coś przekazać poza klasę to mam wybór - referencja albo kopia ale przy tym korzystam ze wszystkich plusów stosu które już wyżej zostały podane. Przynajmniej tak mi się w tej chwili wydaje ;)
Jeśli obiekt klasy będzie na stercie to wszystkie jego pola będą na stercie, to dość oczywiste i tak jest zawsze. Pola obiektu zawsze będą tam gdzie obiekt.
Jeśli obiekt alokuje pamięć przy pomocy new/malloc to pola obiektu nadal będą tam gdzie obiekt, ale zaalokowany obszar zawsze będzie na stercie.