Además de las otras respuestas, me gustaría agregar que al dividir la RAM entre la pila y el espacio de almacenamiento dinámico, también debe tener en cuenta el espacio para datos estáticos no constantes (por ejemplo, archivos globales, estadísticas de funciones y todo el programa). globales desde una perspectiva C, y probablemente otros para C ++).
Cómo funciona la asignación de pila / montón
Vale la pena señalar que el archivo de ensamblaje de inicio es una forma de definir la región; la cadena de herramientas (tanto su entorno de compilación como el entorno de tiempo de ejecución) se preocupan principalmente por los símbolos que definen el inicio del espacio de apilamiento (utilizado para almacenar el puntero de pila inicial en la Tabla de vectores) y el inicio y el final del espacio de almacenamiento dinámico (utilizado por la dinámica asignador de memoria, generalmente proporcionado por su libc)
En el ejemplo de OP, solo se definen 2 símbolos, un tamaño de pila en 1 kB y un tamaño de montón en 0 B. Estos valores se utilizan en otros lugares para producir realmente los espacios de pila y montón
En el ejemplo de @Gilles, los tamaños se definen y usan en el archivo de ensamblaje para establecer un espacio de pila comenzando donde sea y durando el tamaño, identificado por el símbolo Stack_Mem y establece una etiqueta __initial_sp al final. Del mismo modo para el montón, donde el espacio es el símbolo Heap_Mem (0.5 kB de tamaño), pero con etiquetas al principio y al final (__heap_base y __heap_limit).
Estos son procesados por el enlazador, que no asignará nada dentro del espacio de pila y espacio de almacenamiento dinámico porque esa memoria está ocupada (por los símbolos Stack_Mem y Heap_Mem), pero puede colocar esas memorias y todos los globales donde sea necesario. Las etiquetas terminan siendo símbolos sin longitud en las direcciones dadas. __Initial_sp se usa directamente para la tabla de vectores en el momento del enlace, y __heap_base y __heap_limit por su código de tiempo de ejecución. El vinculador asigna las direcciones reales de los símbolos en función de dónde las colocó.
Como mencioné anteriormente, estos símbolos en realidad no tienen que venir de un archivo startup.s. Pueden provenir de la configuración de su enlazador (Scatter Load file en Keil, linkerscript en GNU), y en aquellos puede tener un control más fino sobre la ubicación. Por ejemplo, puede forzar que la pila esté al principio o al final de la RAM, o mantener sus glóbulos alejados del montón, o lo que quiera. Incluso puede especificar que HEAP o STACK solo ocupen la RAM restante después de colocar los globals. Sin embargo, tenga en cuenta que debe tener cuidado de que al agregar más variables estáticas que disminuya su otra memoria.
Sin embargo, cada cadena de herramientas es diferente, y la forma de escribir el archivo de configuración y los símbolos que usará su asignador de memoria dinámica deberán provenir de la documentación de su entorno particular.
Tamaño de pila
En cuanto a cómo determinar el tamaño de la pila, muchas cadenas de herramientas pueden darle una profundidad máxima de la pila analizando los árboles de llamadas de función de su programa, SI no usa punteros de recursión o función. Si los usa, estimando un tamaño de pila y llenándolo previamente con valores cardinales (quizás a través de la función de entrada antes de main) y luego verificando después de que su programa se haya ejecutado durante un tiempo donde estaba la profundidad máxima (que es donde están los valores cardinales final). Si ha ejercido completamente su programa hasta sus límites, sabrá con bastante precisión si puede reducir la pila o, si su programa falla o no quedan valores cardinales, necesita aumentar la pila e intentar nuevamente.
Tamaño del montón
Determinar el tamaño del almacenamiento dinámico depende un poco más de la aplicación. Si solo realiza una asignación dinámica durante el inicio, puede agregar el espacio requerido en su código de inicio (más algunos gastos generales para la administración de la memoria). Si tiene acceso a la fuente de su administrador de memoria, puede saber exactamente cuál es la sobrecarga y posiblemente incluso escribir código para recorrer la memoria y darle información de uso. Para las aplicaciones que necesitan memoria dinámica de tiempo de ejecución (p. Ej., Asignando buffers para tramas de ethernet entrantes), lo mejor que puedo sugerir es que afine cuidadosamente su tamaño de pila y le dé al montón todo lo que queda después de la pila y las estadísticas.
Nota final (RTOS)
La pregunta de OP fue etiquetada para metal desnudo, pero quiero agregar una nota para RTOS. A menudo (¿siempre?) A cada tarea / proceso / hilo (simplemente escribiré la tarea aquí por simplicidad) se le asignará un tamaño de pila cuando se crea la tarea, y además de las pilas de tareas, es probable que haya un sistema operativo pequeño stack (usado para interrupciones y tal)
Las estructuras de contabilidad de tareas y las pilas deben asignarse desde algún lugar, y esto a menudo será desde el espacio de almacenamiento dinámico general de su aplicación. En estos casos, su tamaño inicial de pila a menudo no importará, porque el sistema operativo solo lo usará durante la inicialización. He visto, por ejemplo, especificar TODO el espacio restante durante la vinculación se asignará al HEAP y colocar el puntero de la pila inicial al final del montón para crecer en el montón, sabiendo que el sistema operativo asignará comenzando desde el principio del montón y asignará la pila del sistema operativo justo antes de abandonar la pila initial_sp. Luego, todo el espacio se utiliza para asignar pilas de tareas y otra memoria asignada dinámicamente.