Zusätzlich zu den anderen Antworten möchte ich hinzufügen, dass Sie beim Aufteilen des Arbeitsspeichers zwischen Stapel- und Heapspeicher auch den Speicherplatz für statische nicht konstante Daten berücksichtigen müssen (z. B. Dateiglobale, Funktionsstatik und programmweit) Globale aus einer C-Perspektive und wahrscheinlich andere für C ++).
So funktioniert die Stapel- / Heap-Zuordnung
Es ist erwähnenswert, dass die Startassembly-Datei eine Möglichkeit zum Definieren der Region darstellt. Die Toolchain (sowohl Ihre Build-Umgebung als auch die Laufzeitumgebung) kümmert sich hauptsächlich um die Symbole, die den Anfang des Stapelbereichs (der zum Speichern des anfänglichen Stapelzeigers in der Vektortabelle verwendet wird) sowie den Anfang und das Ende des Heap-Bereichs (der von der Dynamik verwendet wird) definieren Speicherzuordnung, normalerweise bereitgestellt von Ihrer libc)
In OPs Beispiel sind nur 2 Symbole definiert, eine Stapelgröße bei 1 kB und eine Heapgröße bei 0B. Diese Werte werden an anderer Stelle verwendet, um die Stapel- und Heap-Räume tatsächlich zu erzeugen
Im Beispiel @Gilles werden die Größen definiert und in der Baugruppendatei verwendet, um einen Stapelbereich festzulegen, der an jeder beliebigen Stelle beginnt und die Größe beibehält. Dieser wird durch das Symbol Stack_Mem gekennzeichnet und am Ende durch eine Bezeichnung __initial_sp gekennzeichnet. Ähnliches gilt für den Heap, bei dem das Leerzeichen das Symbol Heap_Mem (0,5 KB groß) ist, jedoch mit Beschriftungen am Anfang und Ende (__heap_base und __heap_limit).
Diese werden vom Linker verarbeitet, der nichts im Stapelspeicher und im Heapspeicher zuweist, da dieser Speicher belegt ist (durch die Symbole Stack_Mem und Heap_Mem), aber er kann diese Speicher und alle globalen Speicher an beliebiger Stelle platzieren. Die Bezeichnungen sind an den angegebenen Adressen Symbole ohne Länge. Der __initial_sp wird zur Verbindungszeit direkt für die Vektortabelle verwendet, und die __heap_base und __heap_limit werden von Ihrem Laufzeitcode verwendet. Die tatsächlichen Adressen der Symbole werden vom Linker anhand der Position zugewiesen, an der sie platziert wurden.
Wie oben bereits erwähnt, müssen diese Symbole nicht unbedingt aus einer startup.s-Datei stammen. Sie können aus Ihrer Linkerkonfiguration stammen (Scatter Load-Datei in Keil, Linkerscript in GNU) und in diesen können Sie die Platzierung genauer steuern. Sie können beispielsweise festlegen, dass sich der Stapel am Anfang oder Ende des Arbeitsspeichers befindet, oder Ihre globalen Daten vom Heap fernhalten oder was auch immer Sie möchten. Sie können sogar festlegen, dass HEAP oder STACK nur den verbleibenden Arbeitsspeicher belegen, nachdem Globals platziert wurden. Beachten Sie jedoch, dass Sie darauf achten müssen, dass mehr statische Variablen hinzugefügt werden, die Ihren anderen Speicher verringern.
Jede Toolchain ist jedoch unterschiedlich, und wie die Konfigurationsdatei geschrieben wird und welche Symbole Ihr dynamischer Speicherzuordner verwendet, muss aus der Dokumentation Ihrer speziellen Umgebung stammen.
Stapelgröße
In Bezug auf die Ermittlung der Stapelgröße können viele Toolchains eine maximale Stapeltiefe liefern, indem sie die Funktionsaufrufbäume Ihres Programms analysieren, WENN Sie keine Rekursions- oder Funktionszeiger verwenden. Wenn Sie diese verwenden, schätzen Sie eine Stapelgröße und füllen sie vorab mit Kardinalwerten (möglicherweise über die Eingabefunktion vor main). Überprüfen Sie dann, nachdem Ihr Programm eine Weile ausgeführt wurde, wo die maximale Tiefe war (wo sich die Kardinalwerte befanden Ende). Wenn Sie Ihr Programm vollständig ausgelastet haben, wissen Sie ziemlich genau, ob Sie den Stapel verkleinern können, oder ob Sie den Stapel vergrößern und es erneut versuchen müssen, wenn Ihr Programm abstürzt oder keine Kardinalwerte mehr vorhanden sind.
Haufengröße
Das Ermitteln der Heap-Größe ist etwas anwendungsabhängiger. Wenn Sie die dynamische Zuordnung nur während des Startvorgangs vornehmen, können Sie nur den in Ihrem Startcode erforderlichen Speicherplatz (zuzüglich eines gewissen Overheads für die Speicherverwaltung) addieren. Wenn Sie Zugriff auf die Quelle Ihres Speichermanagers haben, können Sie den Overhead genau kennen und möglicherweise sogar Code schreiben, um den Speicher zu durchsuchen und Ihnen Nutzungsinformationen zu geben. Für Anwendungen, die dynamischen Laufzeitspeicher benötigen (z. B. das Zuweisen von Puffern für eingehende Ethernet-Frames), kann ich nur empfehlen, die Stapelgröße sorgfältig zu verfeinern und dem Heap alles zu geben, was nach Stapel und Statik übrig bleibt.
Schlussnote (RTOS)
OPs Frage war für Bare-Metal markiert, aber ich möchte einen Hinweis für RTOSes hinzufügen. Oft (immer?) Wird jeder Aufgabe / Prozess / Thread (der Einfachheit halber schreibe ich hier nur die Aufgabe auf) eine Stapelgröße zugewiesen, wenn die Aufgabe erstellt wird. Zusätzlich zu den Aufgabenstapeln wird es wahrscheinlich ein kleines Betriebssystem geben Stack (wird für Interrupts und ähnliches verwendet)
Die Aufgabenabrechnungsstrukturen und die Stapel müssen von einem beliebigen Ort aus zugewiesen werden, und dies wird häufig vom gesamten Heap-Speicherplatz Ihrer Anwendung abhängen. In diesen Fällen spielt Ihre anfängliche Stapelgröße oft keine Rolle, da das Betriebssystem sie nur während der Initialisierung verwendet. Ich habe zum Beispiel gesehen, wie beim Verknüpfen ALLER verbleibender Speicherplatz dem HEAP zugewiesen wurde und der anfängliche Stapelzeiger am Ende des Heapspeichers platziert wurde, um in den Heapspeicher hineinzuwachsen reserviert den OS-Stack kurz vor dem Verlassen des initial_sp-Stacks. Der gesamte Speicherplatz wird dann zum Zuweisen von Taskstapeln und anderem dynamisch zugewiesenen Speicher verwendet.