Oltre alle altre risposte, mi piacerebbe aggiungere che, quando si esegue la memoria RAM tra lo stack e lo spazio heap, è necessario considerare anche lo spazio per i dati statici non costanti (ad es. File globali, statica delle funzioni e per tutto il programma globuli dal punto di vista C, e probabilmente altri per C ++).
Come funziona l'allocazione stack / heap
Vale la pena notare che il file assembly di avvio è un modo per definire la regione; la toolchain (sia l'ambiente di compilazione che l'ambiente di runtime) si preoccupa principalmente dei simboli che definiscono l'inizio dello spazio dello stack (utilizzato per memorizzare il puntatore dello stack iniziale nella tabella vettoriale) e l'inizio e la fine dello spazio dell'heap (utilizzato dalla dinamica allocatore di memoria, generalmente fornito da libc)
Nell'esempio di OP, sono definiti solo 2 simboli, una dimensione dello stack a 1 kB e una dimensione dell'heap a 0 b. Questi valori vengono utilizzati altrove per produrre effettivamente gli spazi stack e heap
Nell'esempio @Gilles, le dimensioni sono definite e utilizzate nel file di assieme per impostare uno spazio di stack che inizia ovunque e che dura la dimensione, identificato dal simbolo Stack_Mem e imposta un'etichetta __initial_sp alla fine. Allo stesso modo per l'heap, dove lo spazio è il simbolo Heap_Mem (0,5 kB di dimensione), ma con etichette all'inizio e alla fine (__heap_base e __heap_limit).
Questi vengono elaborati dal linker, che non alloca nulla nello spazio dello stack e nello spazio dell'heap perché quella memoria è occupata (dai simboli Stack_Mem e Heap_Mem), ma può posizionare quei ricordi e tutti i globi ovunque ne abbia bisogno. Le etichette finiscono per essere simboli senza lunghezza agli indirizzi indicati. __Initial_sp viene utilizzato direttamente per la tabella vettoriale al momento del collegamento e __heap_base e __heap_limit dal codice di runtime. Gli indirizzi effettivi dei simboli sono assegnati dal linker in base a dove sono stati posizionati.
Come ho accennato in precedenza, questi simboli non devono effettivamente provenire da un file startup.s. Possono provenire dalla configurazione del tuo linker (Scatter Load file in Keil, linkerscript in GNU) e in quelli puoi avere un controllo più fine sul posizionamento. Ad esempio, puoi forzare lo stack all'inizio o alla fine della RAM, oppure tenere i globali lontani dall'heap o da qualunque cosa tu voglia. Puoi anche specificare che HEAP o STACK occupano solo la RAM rimasta dopo il posizionamento dei globi. NOTA, tuttavia, è necessario fare attenzione che l'aggiunta di più variabili statiche che ridurrà l'altra memoria.
Tuttavia, ogni toolchain è diverso e il modo in cui scrivere il file di configurazione e quali simboli utilizzerà l'allocatore di memoria dinamica dovrà provenire dalla documentazione del proprio ambiente specifico.
Ridimensionamento dello stack
Per quanto riguarda come determinare la dimensione dello stack, molte toolchain possono darti una profondità massima dello stack analizzando gli alberi delle chiamate di funzione del tuo programma, SE non usi ricorsione o puntatori di funzione. Se li usi, stimando una dimensione dello stack e pre-riempendolo con i valori cardinali (forse tramite la funzione di immissione prima del principale) e quindi controllando dopo che il programma è stato eseguito per un po 'di tempo in cui si trovava la profondità massima (che è dove i valori cardinali fine). Se hai completamente esercitato il tuo programma fino ai suoi limiti, saprai in modo abbastanza preciso se puoi ridurre lo stack o, se il tuo programma si arresta in modo anomalo o non sono rimasti valori cardinali, devi aumentare lo stack e riprovare.
Dimensionamento dell'heap
Determinare la dimensione dell'heap dipende un po 'più dall'applicazione. Se esegui l'allocazione dinamica solo durante l'avvio, puoi semplicemente aggiungere lo spazio richiesto nel codice di avvio (oltre a un sovraccarico per la gestione della memoria). Se hai accesso alla fonte del tuo gestore della memoria, puoi sapere esattamente qual è l'overhead e possibilmente anche scrivere il codice per percorrere la memoria per darti informazioni sull'utilizzo. Per le applicazioni che richiedono memoria di runtime dinamica (ad es. Allocazione di buffer per frame ethernet in entrata) il meglio che posso suggerire è di affinare con cura il vostro stack e dare a Heap tutto ciò che resta dopo stack e statica.
Nota finale (RTOS)
La domanda di OP è stata taggata per bare-metal, ma voglio aggiungere una nota per RTOS. Spesso (sempre?) A ogni attività / processo / thread (scriverò semplicemente l'attività qui per semplicità) verrà assegnata una dimensione dello stack quando l'attività viene creata, oltre a stack di attività, probabilmente ci sarà un piccolo sistema operativo stack (usato per interrupt e simili)
Le strutture di contabilità delle attività e le pile devono essere allocate da qualche parte, e questo sarà spesso dallo spazio heap complessivo dell'applicazione. In questi casi, le dimensioni iniziali dello stack spesso non contano, poiché il sistema operativo lo utilizzerà solo durante l'inizializzazione. Ho visto, ad esempio, specificare TUTTO lo spazio rimanente durante il collegamento da allocare all'HEAP e posizionare il puntatore dello stack iniziale alla fine dell'heap per crescere nell'heap, sapendo che il sistema operativo si allocerà a partire dall'inizio dell'heap e assegnerà lo stack del sistema operativo appena prima di abbandonare lo stack initial_sp. Quindi tutto lo spazio viene utilizzato per allocare stack di attività e altra memoria allocata dinamicamente.