Ile zużycia stosu jest za dużo?

22

Ostatnio, gdy piszę C lub C ++, zadeklaruję wszystkie moje zmienne na stosie tylko dlatego, że jest to opcja, w przeciwieństwie do Javy.

Jednak słyszałem, że deklarowanie dużych rzeczy na stosie jest złym pomysłem.

  1. Dlaczego dokładnie tak jest w tym przypadku? Sądzę, że w grę wchodzi przepełnienie stosu, ale nie bardzo wiem, dlaczego tak się dzieje.
  2. Ile rzeczy na stosie to za dużo?

Nie próbuję umieszczać 100 MB plików na stosie, tylko kilkanaście kilobajtów tablic do wykorzystania jako bufory łańcuchów lub cokolwiek innego. Czy to zbyt duże zużycie stosu?

(Przepraszam, jeśli zduplikowane, wyszukiwanie stosu wciąż zawiera odniesienia do Przepełnienia stosu. Nie ma nawet znacznika stosu wywołań, po prostu użyłem abstrakcyjnego).

Elliot Way
źródło
1
Jak „umieścić 100 MB plików na stosie”? Implementacje buforów i kontenerów (i podobne jak std :: string) zwykle używają sterty do przechowywania ich ładunku.
Murphy,
2
Możesz uniknąć sporego zużycia stosu na funkcję / metodę, dopóki nie pojawi się rekurencja, wtedy ryzykujesz poważnym ograniczeniem swoich możliwości względem rekurencyjnej głębokości, więc w ramach funkcji rekurencyjnych chcesz używać jak najmniej lokalnych przestrzeń zmiennej / stosu, jak to możliwe.
Erik Eidt
3
Zauważ, że C i C ++ są różne. std::vector<int>Zmienna lokalna nie zje dużo miejsca na stosie, ponieważ większość danych jest w stercie.
Basile Starynkevitch

Odpowiedzi:

18

To zależy od twojego systemu operacyjnego. W systemie Windows typowy maksymalny rozmiar stosu wynosi 1 MB, podczas gdy w typowym nowoczesnym systemie Linux jest to 8 MB, chociaż wartości te można regulować na różne sposoby. Jeśli suma zmiennych stosu (w tym narzut niskiego poziomu, takich jak adresy zwrotne, argumenty oparte na stosie, symbole zastępcze wartości zwracanej i bajty wyrównania) w całym stosie wywołań przekracza ten limit, otrzymasz przepełnienie stosu, co zwykle zmniejsza program bez szans na odzyskanie.

Kilka kilobajtów jest zwykle w porządku. Dziesiątki kilobajtów jest niebezpieczne, ponieważ zaczyna się sumować. Setki kilobajtów to bardzo zły pomysł.

Sebastian Redl
źródło
1
Czy typowy stos nie ogranicza dziś kilku megabajtów (tj. Zwykle więcej niż jednego, ale prawdopodobnie mniej niż tuzina) w 2016 roku? Na moim komputerze z systemem Linux jest to domyślnie 8 MB ...
Basile Starynkevitch
„W [...] Linuksie typowy maksymalny rozmiar stosu wynosi 1 MB” $ ulimit -aw moim systemie zwraca między innymi stack size (kbytes, -s) 8192.
Murphy
9

Jedyną prawidłową odpowiedzią jest niejasna: „za dużo jest, gdy stos się przepełnia”.

O ile nie masz pełnej kontroli nad implementacją każdego wiersza kodu między punktem wejścia programu a funkcją, o której mowa, nie możesz przyjmować żadnych założeń dotyczących ilości dostępnego stosu. Nie można na przykład zagwarantować, że wywołanie tej funkcji nigdy nie spowoduje przepełnienia stosu:

void break_the_camels_back()
{
    int straw;
    ...
}

Domyślny stos 8 MiB we współczesnych Uniksach to dość dużo miejsca w stosach, szczególnie dla kogoś takiego jak ja, który ma dość geezera, aby zapamiętać procesory z 8-bitowymi wskaźnikami stosu. Praktyczna rzeczywistość jest taka, że ​​prawdopodobnie nie przebijesz jej bez próby. Jeśli to zrobisz, przekroczenie limitu stosu jest zwykle uważane za naruszenie segmentacji, a systemy z wystarczającym zarządzaniem pamięcią do wykrycia go wyśląSIGSEGV gdy to nastąpi.

Masz kilka opcji. Po pierwsze, nie zgadnij, ile stosu jest dostępnych i zapytaj system. Wszystko zgodne z POSIX będzie miało getrlimit(2)funkcję, która powie ci górną granicę. RLIMIT_STACKto konkretny limit, jaki chcesz. Drugim jest monitorowanie, ile stosu używają twoje programy, i podejmowanie decyzji dotyczących zmiennych automatycznych w porównaniu do dynamicznej alokacji pamięci na tej podstawie. O ile mi wiadomo, nie ma standardowych funkcji określających, ile stosu jest używany, ale programy takie valgrindmogą go dla ciebie przeanalizować.

Blrfl
źródło
4

Jeśli przydzielisz tablicę powiedzmy 10 000 bajtów na stosie, rozmiar tej tablicy jest ograniczony. 10 000 może być dużo, ale jeśli potrzebujesz 10 001 bajtów, Twój program może ulec awarii lub gorzej. Więc w tej sytuacji potrzebujesz czegoś, co dostosuje się do potrzebnego rozmiaru i że czegoś nie będzie na stosie.

Tablice o stałych rozmiarach dla buforów łańcuchowych na stosie nie stanowią problemu, ponieważ utrzymują pamięć na stosie, są problemem, ponieważ bufory o stałych rozmiarach są śmiertelnym problemem, który czeka.

Ale jeśli użyjesz C ++ i zadeklarujesz na stosie std :: string lub std :: vec na stosie, wówczas to, co znajduje się na stosie, będzie miało rzeczywiście stały i mały rozmiar. Rzeczywiste dane będą przechowywane na stercie. Możesz przechowywać milion znaków w instancji std :: string i zajmie to bardzo niewielką ilość danych (zwykle 8 do 24 bajtów, w zależności od implementacji) na stosie i milion bajtów na stercie.

gnasher729
źródło
2

Cóż, 1 MB to dobry szacunek dla * nix. Rekurencja może być głównym powodem przepełnienia stosu w połączeniu z przydziałami stosu. Jednak w większości przypadków boskie obiekty, które pozornie wydają się zbyt duże, aby mogły zostać umieszczone na stosie, są dobrze zaprojektowane do zarządzania pamięcią wewnętrzną na stosie i używają stosu tylko jako sposobu na automatyczne zniszczenie, gdy stos zostanie zdjęty. Destruktor uwolni ogromne fragmenty pamięci zarządzanej wewnętrznie. Kontenery standardowe są zaprojektowane w ten sposób, a wspólne / unikalne wskaźniki również są zaprojektowane w ten sposób.

Ważne jest, aby nie przydzielać dużych porcji surowej pamięci na stosie, takich jak char [1024 * 1024], i projektować klasy, które będą zawijać przydziały stosów i używać stosu tylko dla wygody automatycznego wywoływania destruktora.

Gwiazdka
źródło