C/C++ maximale Stapelgröße des Programms

Lesezeit: 14 Minuten

CC maximale Stapelgrose des Programms
Avd

Ich möchte DFS auf einem 100 x 100-Array ausführen. (Angenommen, Elemente eines Arrays stellen Diagrammknoten dar.) Unter der Annahme des schlimmsten Falls kann die Tiefe rekursiver Funktionsaufrufe bis zu 10000 betragen, wobei jeder Aufruf bis zu sagen wir 20 Bytes benötigt. Ist es also machbar, besteht die Möglichkeit eines Stapelüberlaufs?

Was ist die maximale Stapelgröße in C/C++?

Bitte gcc für beide angeben
1) cygwin unter Windows
2) Unix

Was sind die allgemeinen Grenzen?

  • Sie wissen, dass Sie die Tiefensuche ohne Rekursion implementieren können, oder?

    – Sebastian

    1. Dezember 2009 um 12:55 Uhr

  • Nein, ich weiß nicht, bitte erklären.

    – durchschn

    1. Dezember 2009 um 12:56 Uhr

  • Ich habe in meiner Antwort ein kleines Beispiel für DFS ohne Rekursion gemacht

    – Andreas Brink

    1. Dezember 2009 um 13:04 Uhr

CC maximale Stapelgrose des Programms
Andreas Brink

In Visual Studio beträgt die Standardstapelgröße meiner Meinung nach 1 MB. Bei einer Rekursionstiefe von 10.000 kann jeder Stapelrahmen höchstens ~ 100 Bytes umfassen, was für einen DFS-Algorithmus ausreichen sollte.

Bei den meisten Compilern, einschließlich Visual Studio, können Sie die Stapelgröße angeben. Bei einigen (allen?) Linux-Varianten ist die Stapelgröße nicht Teil der ausführbaren Datei, sondern eine Umgebungsvariable im Betriebssystem. Sie können dann die Stapelgröße mit überprüfen ulimit -s und setzen Sie ihn beispielsweise mit auf einen neuen Wert ulimit -s 16384.

Hier ist ein Verknüpfung mit Standardstapelgrößen für gcc.

DFS ohne Rekursion:

std::stack<Node> dfs;
dfs.push(start);
do {
    Node top = dfs.top();
    if (top is what we are looking for) {
       break;
    }
    dfs.pop();
    for (outgoing nodes from top) {
        dfs.push(outgoing node);
    }
} while (!dfs.empty())

  • Und nur als Referenz, ein BFS ist dasselbe, außer dass Sie einen FIFO anstelle eines Stapels verwenden.

    – Steve Jessop

    1. Dezember 2009 um 14:39 Uhr

  • Ja, oder verwenden Sie im STL-Jargon ein std::deque mit pop_front/push_back

    – Andreas Brink

    1. Dezember 2009 um 15:07 Uhr

  • Ihr DFS mit Stack-Ergebnissen unterscheidet sich von der Rekursionsversion. In einigen Fällen spielt es keine Rolle, aber in anderen (z. B. bei der topologischen Sortierung) erhalten Sie falsche Ergebnisse

    – spin_acht

    29. April 2016 um 8:16 Uhr

  • Ja, das Standardlimit für VS ist tatsächlich 1 MB. Weitere Informationen und die Möglichkeit, einen anderen Wert festzulegen, finden Sie in der Microsoft-Dokumentation: msdn.microsoft.com/en-us/library/tdkhxaks(v=vs.140).aspx

    – FrankS101

    5. Oktober 2016 um 15:02 Uhr

  • Ich ziehe es vor, für solche Algorithmen eine explizite Stack-Datenstruktur zu verwenden, anstatt Rekursion, damit 1. nicht von der Größe des Systemstapels abhängen, 2. der Algorithmus geändert werden kann, um eine andere Datenstruktur zu verwenden, z. B. Warteschlange oder Prioritätswarteschlange, ohne zu werfen den ganzen Code aus.

    – Sam Watkins

    20. März 2018 um 2:08 Uhr

1647174016 82 CC maximale Stapelgrose des Programms
Pixelschlag

Stacks für Threads sind oft kleiner. Sie können die Standardeinstellung zum Zeitpunkt der Verknüpfung oder auch zur Laufzeit ändern. Als Referenz sind einige Standardwerte:

  • glibc i386, x86_64: 7,4 MB
  • Tru64 5.1: 5,2MB
  • Cygwin: 1,8 MB
  • Solaris 7..10: 1MB
  • MacOS X 10.5: 460 KB
  • AIX5: 98 KB
  • OpenBSD 4.0: 64 KB
  • HP-UX 11: 16 KB

  • Empirisch bestimmt von Bruno Haible listen.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html

    – Pixelschlag

    1. September 2014 um 10:50 Uhr

  • Ich habe den Code und die Zitate von Bruno Haible in meine neue Antwort hier eingefügt und gezeigt, wie Sie Ihr eigenes System mit seinem Code manuell testen können: stackoverflow.com/a/64085509/4561887.

    – Gabriel Staples

    27. September 2020 um 7:48 Uhr

Plattformabhängig, Toolchain-abhängig, ulimit-abhängig, Parameter-abhängig…. Es ist überhaupt nicht spezifiziert, und es gibt viele statische und dynamische Eigenschaften, die es beeinflussen können.

  • Es gibt keine “allgemeinen Grenzen”. Unter Windows mit standardmäßigen VC++-Linkeroptionen und standardmäßigem CreateThread-Verhalten, normalerweise etwa 1 MiB pro Thread. Unter Linux mit einer unbegrenzten Anzahl von Benutzern gibt es meines Erachtens normalerweise keine Begrenzung (der Stapel kann einfach nach unten wachsen, um fast den gesamten Adressraum zu belegen). Wenn Sie fragen müssen, sollten Sie den Stack grundsätzlich nicht verwenden.

    – DrPizza

    1. Dezember 2009 um 12:49 Uhr

  • Auf eingebetteten Systemen haben Sie möglicherweise 4k oder weniger. In diesem Fall müssen Sie fragen, auch wenn es sinnvoll ist, den Stack zu verwenden. Die Antwort ist meist ein gallisches Achselzucken.

    – Steve Jessop

    1. Dezember 2009 um 14:43 Uhr

  • Ah stimmt, auch oft im Kernelmodus der Fall.

    – DrPizza

    1. Dezember 2009 um 22:49 Uhr

Ja, es besteht die Möglichkeit eines Stapelüberlaufs. Der C- und der C++-Standard schreiben keine Dinge wie die Stapeltiefe vor, diese sind im Allgemeinen ein Umweltproblem.

Mit den meisten anständigen Entwicklungsumgebungen und/oder Betriebssystemen können Sie die Stapelgröße eines Prozesses anpassen, entweder zur Link- oder Ladezeit.

Für eine gezieltere Unterstützung sollten Sie angeben, welches Betriebssystem und welche Entwicklungsumgebung Sie verwenden.

Beispielsweise ist unter Ubuntu Karmic Koala die Standardeinstellung für gcc 2 MB reserviert und 4 KB festgeschrieben, aber dies kann geändert werden, wenn Sie das Programm verknüpfen. Verwenden Sie die --stack Option von ld das zu tun.

Mir ist gerade bei der Arbeit der Stack ausgegangen, es war eine Datenbank und es lief einige Threads, im Grunde hatte der vorherige Entwickler ein großes Array auf den Stack geworfen, und der Stack war sowieso niedrig. Die Software wurde mit Microsoft Visual Studio 2015 kompiliert.

Obwohl dem Thread der Stack ausgegangen war, schlug er stillschweigend fehl und fuhr fort, er lief nur über, wenn es darum ging, auf den Inhalt der Daten auf dem Stack zuzugreifen.

Der beste Rat, den ich geben kann, ist, Arrays nicht auf dem Stapel zu deklarieren – insbesondere in komplexen Anwendungen und insbesondere in Threads, verwenden Sie stattdessen Heap. Dafür ist es da 😉

Denken Sie auch daran, dass es möglicherweise nicht sofort fehlschlägt, wenn der Stack deklariert wird, sondern nur beim Zugriff. Meine Vermutung ist, dass der Compiler den Stack unter Windows “optimistisch” deklariert, dh er geht davon aus, dass der Stack deklariert wurde und ausreichend groß ist, bis er verwendet wird, und findet dann heraus, dass der Stack nicht vorhanden ist.

Unterschiedliche Betriebssysteme können unterschiedliche Stack-Deklarationsrichtlinien haben. Bitte hinterlassen Sie einen Kommentar, wenn Sie wissen, was diese Richtlinien sind.

1647174017 517 CC maximale Stapelgrose des Programms
David Kirby

Ich bin mir nicht sicher, was Sie meinen, wenn Sie eine Tiefensuche in einem rechteckigen Array durchführen, aber ich gehe davon aus, dass Sie wissen, was Sie tun.

Wenn das Stapellimit ein Problem darstellt, sollten Sie in der Lage sein, Ihre rekursive Lösung in eine iterative Lösung umzuwandeln, die Zwischenwerte auf einen Stapel legt, der vom Heap zugewiesen wird.

(Hinzugefügt am 26. Sept. 2020)

Am 24. Okt. 2009, wie @pixelbeat hier zum ersten Mal erwähnte, Bruno Haible hat empirisch die folgenden Standard-Thread-Stack-Größen entdeckt für mehrere Systeme. Er hat das gesagt In einem Multithread-Programm ist “die Standard-Thread-Stack-Größe:”

- glibc i386, x86_64    7.4 MB
- Tru64 5.1             5.2 MB
- Cygwin                1.8 MB
- Solaris 7..10           1 MB
- MacOS X 10.5          460 KB
- AIX 5                  98 KB
- OpenBSD 4.0            64 KB
- HP-UX 11               16 KB

Beachten Sie, dass die oben genannten Einheiten alle in MB und KB (Zahlen zur Basis 1000) angegeben sind, NICHT in MiB und KiB (Zahlen zur Basis 1024). Ich habe mir das selbst bewiesen, indem ich den 7,4-MB-Fall verifiziert habe.

Er erklärte auch, dass:

32 KB sind mehr, als Sie in einem Multithread-Programm sicher auf dem Stack zuweisen können

Und er sagte:

Und die Standardstapelgröße für sigaltstack, SIGSTKSZ, ist

  • nur 16 KB auf einigen Plattformen: IRIX, OSF/1, Haiku.
  • nur 8 KB auf einigen Plattformen: glibc, NetBSD, OpenBSD, HP-UX, Solaris.
  • nur 4 KB auf einigen Plattformen: AIX.

Bruno

Er schrieb das folgende einfache Linux-C-Programm, um die obigen Werte empirisch zu bestimmen. Sie können es heute auf Ihrem System ausführen, um schnell zu sehen, wie groß Ihre maximale Thread-Stack-Größe ist, oder Sie können es hier online auf GDBOnline ausführen: https://onlinegdb.com/rkO9JnaHD.

Erläuterung: Es erstellt einfach einen einzigen neuen Thread, um das zu überprüfen Größe des Threadstapels und NICHT die Programmstapelgrößefalls sie sich unterscheiden, muss dieser Thread wiederholt 128 Byte Speicher zuweisen auf dem Stapel (NICHT auf dem Haufen)Verwendung der Linux alloca() Anruf, danach schreibt es eine 0 in das erste Byte dieses neuen Speicherblocks und gibt dann aus, wie viele Bytes es insgesamt zugewiesen hat. Es wiederholt diesen Vorgang und weist 128 weitere Bytes zu auf dem Stapel jedes Mal, bis das Programm mit a abstürzt Segmentation fault (core dumped) Error. Der zuletzt gedruckte Wert ist die geschätzte maximale Thread-Stack-Größe, die für Ihr System zulässig ist.

Wichtiger Hinweis: alloca() ordnet zu auf dem Stapel: obwohl dies sieht aus wie dynamische Speicherzuordnung auf dem Heap, ähnlich wie a malloc() Anruf, alloca() ordnet dem Heap NICHT dynamisch zu. Eher, alloca() ist eine spezialisierte Linux-Funktion zur “pseudodynamischen” (ich bin mir nicht sicher, wie ich das nennen würde, also habe ich diesen Begriff gewählt) direkt zuzuweisen auf den Stapel als wäre es statisch zugewiesener Speicher. Stapelspeicher verwendet und zurückgegeben von alloca() ist am Geltungsbereich Funktionsebeneund wird daher “automatisch freigegeben, wenn die Funktion das rief alloca() kehrt zu seinem Aufrufer zurück.” Deshalb wird sein statischer Gültigkeitsbereich nicht verlassen und Speicher von zugewiesen alloca() wird NICHT jedes Mal freigegeben a for Schleifeniteration ist abgeschlossen und das Ende der for Schleifenbereich erreicht ist. Sehen man 3 alloca für Details. Hier ist das entsprechende Zitat (Hervorhebung hinzugefügt):

BEZEICHNUNG
Die alloca() Funktion zuweist Größe Byte Platz in der Stapelrahmen des Aufrufers. Dieser temporäre Speicherplatz wird automatisch freigegeben, wenn die Funktion das rief alloca() kehrt zurück zu seinem Anrufer.

RÜCKGABEWERT
Die alloca() Die Funktion gibt einen Zeiger auf den Anfang des zugewiesenen Speicherplatzes zurück. Wenn die Zuweisung zu einem Stapelüberlauf führt, ist das Programmverhalten undefiniert.

Hier das Programm von Bruno Haible vom 24. Okt. 2009, direkt von der GNU-Mailingliste hier kopiert:

Auch hier können Sie Führen Sie es hier live online aus.

// By Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html

// =============== Program for determining the default thread stack size =========

#include <alloca.h>
#include <pthread.h>
#include <stdio.h>
void* threadfunc (void*p) {
  int n = 0;
  for (;;) {
    printf("Allocated %d bytes\n", n);
    fflush(stdout);
    n += 128;
    *((volatile char *) alloca(128)) = 0;
  }
}

int main()
{
  pthread_t thread;
  pthread_create(&thread, NULL, threadfunc, NULL);
  for (;;) {}
}

Wenn ich es über den obigen Link auf GDBOnline ausführe, erhalte ich jedes Mal, wenn ich es ausführe, genau die gleichen Ergebnisse, sowohl als C- als auch als C++17-Programm. Es dauert ungefähr 10 Sekunden oder so, um zu laufen. Hier sind die letzten paar Zeilen der Ausgabe:

Allocated 7449856 bytes
Allocated 7449984 bytes
Allocated 7450112 bytes
Allocated 7450240 bytes
Allocated 7450368 bytes
Allocated 7450496 bytes
Allocated 7450624 bytes
Allocated 7450752 bytes
Allocated 7450880 bytes
Segmentation fault (core dumped)

Die Thread-Stack-Größe beträgt für dieses System also ~7,45 MB, wie Bruno oben erwähnt hat (7,4 MB).

Ich habe ein paar Änderungen am Programm vorgenommen, hauptsächlich nur aus Gründen der Übersichtlichkeit, aber auch aus Gründen der Effizienz und ein wenig zum Lernen.

Zusammenfassung meiner Änderungen:

  1. [learning] Ich bin vorbeigekommen BYTES_TO_ALLOCATE_EACH_LOOP als Argument für die threadfunc() nur zum Üben der Weitergabe und Verwendung von Generika void* Argumente in C.

    1. Hinweis: Dies ist auch der erforderliche Funktionsprototyp, wie von gefordert der pthread_create() Funktionfür die Callback-Funktion (threadfunc() in meinem Fall) weitergegeben pthread_create(). Sehen: https://www.man7.org/linux/man-pages/man3/pthread_create.3.html.
  2. [efficiency] Ich habe den Hauptthread in den Ruhezustand versetzt, anstatt ihn verschwenderisch zu drehen.

  3. [clarity] Ich habe ausführlichere Variablennamen hinzugefügt, wie z BYTES_TO_ALLOCATE_EACH_LOOP und bytes_allocated.

  4. [clarity] Ich habe das geändert:

     *((volatile char *) alloca(128)) = 0;
    

    dazu:

     volatile uint8_t * byte_buff = 
             (volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
     byte_buff[0] = 0;
    

Hier ist mein modifiziertes Testprogramm, das genau das Gleiche tut wie das von Bruno und sogar die gleichen Ergebnisse hat:

Du kannst Führen Sie es hier online ausoder Laden Sie es hier von meinem Repo herunter. Wenn Sie es lokal von meinem Repo ausführen möchten, hier sind die Build- und Run-Befehle, die ich zum Testen verwendet habe:

  1. Erstellen und führen Sie es als C-Programm aus:

     mkdir -p bin && \
     gcc -Wall -Werror -g3 -O3 -std=c11 -pthread -o bin/tmp \
     onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
     time bin/tmp
    
  2. Erstellen und führen Sie es als C++-Programm aus:

     mkdir -p bin && \
     g++ -Wall -Werror -g3 -O3 -std=c++17 -pthread -o bin/tmp \
     onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
     time bin/tmp
    

Es dauert < 0,5 Sekunden, um auf einem schnellen Computer mit einer Thread-Stack-Größe von ~7,4 MB lokal ausgeführt zu werden.

Hier ist das Programm:

// =============== Program for determining the default thread stack size =========

// Modified by Gabriel Staples, 26 Sept. 2020

// Originally by Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html

#include <alloca.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h> // sleep

/// Thread function to repeatedly allocate memory within a thread, printing
/// the total memory allocated each time, until the program crashes. The last
/// value printed before the crash indicates how big a thread's stack size is.
///
/// Note: passing in a `uint32_t` as a `void *` type here is for practice,
/// to learn how to pass in ANY type to a func by using a `void *` parameter.
/// This is also the required function prototype, as required by the
/// `pthread_create()` function, for the callback function (this function)
/// passed to `pthread_create()`. See:
/// https://www.man7.org/linux/man-pages/man3/pthread_create.3.html
void* threadfunc(void* bytes_to_allocate_each_loop)
{
    const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP =
            *(uint32_t*)bytes_to_allocate_each_loop;

    uint32_t bytes_allocated = 0;
    while (true)
    {
        printf("bytes_allocated = %u\n", bytes_allocated);
        fflush(stdout);
        // NB: it appears that you don't necessarily need `volatile` here,
        // but you DO definitely need to actually use (ex: write to) the
        // memory allocated by `alloca()`, as we do below, or else the
        // `alloca()` call does seem to get optimized out on some systems,
        // making this whole program just run infinitely forever without
        // ever hitting the expected segmentation fault.
        volatile uint8_t * byte_buff =
                (volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
        byte_buff[0] = 0;
        bytes_allocated += BYTES_TO_ALLOCATE_EACH_LOOP;
    }
}

int main()
{
    const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP = 128;

    pthread_t thread;
    pthread_create(&thread, NULL, threadfunc,
                   (void*)(&BYTES_TO_ALLOCATE_EACH_LOOP));
    while (true)
    {
        const unsigned int SLEEP_SEC = 10000;
        sleep(SLEEP_SEC);
    }

    return 0;
}

Beispielausgabe (gleiche Ergebnisse wie das ursprüngliche Programm von Bruno Haible):

bytes_allocated = 7450240                                                                                                                                                        
bytes_allocated = 7450368                                                                                                                                                        
bytes_allocated = 7450496                                                                                                                                                        
bytes_allocated = 7450624                                                                                                                                                        
bytes_allocated = 7450752                                                                                                                                                        
bytes_allocated = 7450880                                                                                                                                                        
Segmentation fault (core dumped) 

  • Vielen Dank, dass Sie diese Antwort beigetragen haben. Ich habe Brunos Code auch unter Windows ausgeführt und war etwas verwirrt darüber, was genau die Ausgabe darstellte (Windows hat mir keinen Seg-Fault-Fehler gegeben, sondern nur die Konsole geschlossen). Windows mit MinGW erforderlich #include <malloc.h> anstatt #include <alloca.h> das wäre also erwähnenswert. Können wir nicht auch einfach den Seg-Fehler abfangen und diese Zahl ausspucken?

    – Skewjo

    16. April 2021 um 19:51 Uhr


  • @Skewjo, danke für die Info. um Windows-Benutzern zu helfen. Wie fangen Sie einen Seg-Fehler in C ab? (Ich sage nicht, dass man das nicht kann – ich weiß nur nicht wie). Außerdem, was meinst du mit that number wenn du sagst and spit that number out? Würde nicht that number einfach der letzte gedruckte Wert + 128 sein? Wenn ja, welchen zusätzlichen Wert bringt dies (was bedeutet: Warum sollten wir das tun?), Den Seg-Fehler abzufangen und dann die letzte gedruckte Zahl + 128 auszuspucken, anstatt nur auf die letzte gedruckte Zahl zu schauen, wie es bereits getan wurde?

    – Gabriel Staples

    16. April 2021 um 21:46 Uhr


  • Die Größe des Programmstapels bedeutet eigentlich die Größe des Hauptthreadstapels, richtig? Ich bemerke einen Unterschied in der Stapelgröße zwischen dem Hauptthread und dem neu erstellten Arbeitsthread (11169280 vs. 9315968). Haben Sie eine Idee, warum es einen solchen Unterschied gibt?

    – Baiyan Huang

    22. Juni 2021 um 21:26 Uhr

  • @baye, program stack size actually means the main thread stack size, right? Das ist auch meine Vermutung, also denke ich schon, bin mir aber nicht 100% sicher. Was den Unterschied in der Stapelgröße zwischen dem Hauptthread und dem erstellten (Worker-)Thread betrifft, weiß ich nicht, warum es einen Unterschied gibt, aber es überrascht mich nicht, dass ein Unterschied besteht. Erwägen Sie, Ihren Code zu posten und in einer neuen Frage danach zu fragen, falls es noch keine Frage dafür gibt. Wenn Sie dies tun, posten Sie hier einen Link zu Ihrer Frage, damit wir sie sehen können.

    – Gabriel Staples

    22. Juni 2021 um 22:09 Uhr


997560cookie-checkC/C++ maximale Stapelgröße des Programms

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy