Wie viel Stapelverbrauch ist zu viel?

22

In letzter Zeit, als ich C oder C ++ geschrieben habe, werde ich alle meine Variablen auf dem Stapel deklarieren, nur weil es eine Option ist, im Gegensatz zu Java.

Ich habe jedoch gehört, dass es eine schlechte Idee ist, große Dinge auf dem Stapel zu deklarieren.

  1. Warum genau ist das so? Ich nehme an, dass ein Stapelüberlauf vorliegt, bin mir aber nicht ganz sicher, warum das passiert.
  2. Wie viel Zeug auf dem Stapel ist zu viel?

Ich versuche nicht, 100 MB-Dateien auf den Stapel zu legen, sondern nur ein Dutzend Kilobyte-Arrays, die als String-Puffer verwendet werden sollen, oder was auch immer. Ist das zu viel Stack-Nutzung?

(Tut mir leid, wenn Duplikate vorhanden sind und die Suche nach Stack weiterhin Verweise auf Stack Overflow enthält. Es gibt nicht einmal ein Call-Stack-Tag, ich habe nur das abstrakte verwendet.)

Elliot Way
quelle
1
Wie kann man "100MB Dateien auf den Stapel legen"? Puffer- und Containerimplementierungen (und ähnliche wie std :: string) verwenden normalerweise den Heap, um ihre Nutzdaten zu speichern.
Murphy
2
Sie können mit einer angemessenen Menge an Stack-Nutzung pro Funktion / Methode durchkommen, bis es zu einer Rekursion kommt. Dann riskieren Sie, Ihre Fähigkeiten im Vergleich zu rekursiver Tiefe stark einzuschränken. Innerhalb rekursiver Funktionen möchten Sie also möglichst wenig local verwenden variabler / Stapelplatz wie möglich.
Erik Eidt
3
Beachten Sie, dass C & C ++ unterschiedlich sind. Eine lokale std::vector<int>Variable beansprucht nicht viel Stapelspeicher, da sich die meisten Daten im Heap befinden.
Basile Starynkevitch

Antworten:

18

Das hängt von Ihrem Betriebssystem ab. Unter Windows beträgt die typische maximale Größe eines Stapels 1 MB, während sie unter einem typischen modernen Linux 8 MB beträgt, obwohl diese Werte auf verschiedene Arten einstellbar sind. Wenn die Summe Ihrer Stapelvariablen (einschließlich geringem Overhead wie Rückgabeadressen, stapelbasierten Argumenten, Rückgabewertplatzhaltern und Ausrichtungsbytes) im gesamten Aufrufstapel diesen Grenzwert überschreitet, tritt ein Stapelüberlauf auf Programm ohne Chance auf Wiederherstellung.

Ein paar Kilobyte reichen normalerweise aus. Dutzende Kilobyte sind gefährlich, weil sie anfangen zu summieren. Hunderte von Kilobyte sind eine sehr schlechte Idee.

Sebastian Redl
quelle
1
Ist das typische Stack-Limit heute im Jahr 2016 nicht mehrere Megabyte (dh normalerweise mehr als ein, aber wahrscheinlich weniger als ein Dutzend)? Auf meinem Linux-Desktop sind es standardmäßig
8
"Unter [...] Linux beträgt die typische maximale Größe eines Stacks 1 MB" $ ulimit -a, was unter anderem auf meinem System angezeigt wird stack size (kbytes, -s) 8192.
Murphy
9

Die einzig gültige Antwort ist vage: "Zu viel ist, wenn der Stapel überläuft."

Sofern Sie nicht die vollständige Kontrolle über die Implementierung jeder Codezeile zwischen dem Einstiegspunkt des Programms und der betreffenden Funktion haben, können Sie keine Annahmen darüber treffen, wie viel Stapel verfügbar ist. Sie können beispielsweise nicht garantieren, dass der Aufruf dieser Funktion niemals einen Stapelüberlauf verursacht:

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

Der standardmäßige 8-MiB-Stack unter modernen Unix-Betriebssystemen bietet relativ viel Platz, vor allem für jemanden wie mich, der es versteht, sich an CPUs mit 8-Bit-Stack-Zeigern zu erinnern. Die praktische Realität ist, dass es unwahrscheinlich ist, dass Sie es durchblasen, ohne es zu versuchen. Wenn Sie dies tun, wird das Überschreiten des Stack-Limits normalerweise als Segmentierungsverletzung angesehen, und Systeme mit ausreichender Speicherverwaltung, um dies zu erkennen, senden ein, SIGSEGVwenn dies geschieht.

Sie haben einige Möglichkeiten. Erstens nicht erraten, wie viel Stapel verfügbar ist, und das System fragen. Alles, was mit POSIX übereinstimmt, hat eine getrlimit(2)Funktion, die Ihnen die Obergrenze angibt. RLIMIT_STACKist das von Ihnen gewünschte spezifische Limit. Die zweite besteht darin, zu überwachen, wie viel Stack Ihre Programme verwenden, und basierend darauf Entscheidungen über automatische Variablen im Vergleich zur dynamischen Speicherzuweisung zu treffen. Soweit ich weiß, gibt es keine Standardfunktionen, mit denen bestimmt werden kann, wie viel des Stapels verwendet wird, aber Programme wie valgrindkönnen dies für Sie analysieren.

Blrfl
quelle
4

Wenn Sie ein Array mit beispielsweise 10.000 Bytes auf dem Stapel zuweisen, ist die Größe dieses Arrays begrenzt. 10.000 können eine Menge sein, aber wenn Sie 10.0001 Bytes benötigen, kann Ihr Programm abstürzen oder schlimmer sein. In dieser Situation möchten Sie also etwas, das sich an die Größe anpasst, die Sie benötigen, und etwas, das sich nicht auf dem Stapel befindet.

Arrays mit fester Größe für Zeichenfolgenpuffer auf dem Stapel sind kein Problem, da sie den Speicher auf dem Stapel belassen. Sie sind ein Problem, da Puffer mit fester Größe ein schwerwiegendes Problem darstellen, das darauf wartet, ausgeführt zu werden.

Wenn Sie jedoch C ++ verwenden und beispielsweise eine std :: string oder eine std :: vec auf dem Stapel deklarieren, hat das, was sich auf dem Stapel befindet, tatsächlich eine feste und kleine Größe. Die tatsächlichen Daten werden auf dem Heap gespeichert. Sie können eine Million Zeichen in einer std :: string-Instanz speichern, und es werden nur sehr wenige Daten (in der Regel 8 bis 24 Bytes, je nach Implementierung) auf dem Stapel und eine Million Bytes auf dem Heap benötigt.

gnasher729
quelle
2

Naja 1 MB ist eine gute Schätzung für * nix. Rekursion kann ein Hauptgrund für einen Stapelüberlauf in Kombination mit Stapelzuweisungen sein. In den meisten Fällen sind die Gott-Objekte, die oberflächlich zu groß erscheinen, um auf dem Stapel abgelegt zu werden, so konzipiert, dass sie ihren internen Speicher auf dem Haufen gut verwalten und den Stapel nur als eine Möglichkeit verwenden, automatisch zerstört zu werden, wenn der Stapel abgelegt wird. Der Destruktor gibt die riesigen Speicherbereiche frei, die intern verwaltet werden. Die Standardcontainer sind auf diese Weise gestaltet, und die gemeinsam genutzten / eindeutigen Zeiger sind ebenfalls auf diese Weise gestaltet.

Das Wichtigste ist, keine großen Stücke von Raw Mem auf einem Stapel wie char [1024 * 1024] zuzuweisen und Klassen zu entwerfen, die die Heap-Zuweisungen umbrechen und den Stapel nur verwenden, um den Destruktor automatisch aufzurufen.

Sternchen
quelle