В дополнение к другим ответам, я хотел бы добавить, что при разделении ОЗУ между стеком и пространством кучи также необходимо учитывать пространство для статических неконстантных данных (например, глобальные переменные файлов, статические функции и масштаб всей программы). глобальные с точки зрения C и, вероятно, другие для C ++).
Как работает распределение стека / кучи
Стоит отметить, что файл сборки запуска является одним из способов определения региона; набор инструментов (как среда сборки, так и среда выполнения) в основном заботятся о символах, которые определяют начало стекового пространства (используется для хранения начального указателя стека в таблице векторов), а также начало и конец пространства кучи (используется динамическим распределитель памяти, обычно предоставляемый вашим libc)
В примере OP определены только 2 символа: размер стека в 1 КБ и размер кучи в 0 Б. Эти значения используются в другом месте для создания стека и пространства кучи
В примере @Gilles размеры определяются и используются в файле сборки, чтобы установить пространство стека, начинающееся везде и продолжительное время, определяемое символом Stack_Mem и устанавливающее метку __initial_sp в конце. Аналогично для кучи, где пробел является символом Heap_Mem (размером 0,5 кБ), но с метками в начале и конце (__heap_base и __heap_limit).
Они обрабатываются компоновщиком, который не будет выделять что-либо в пространстве стека и пространстве кучи, потому что эта память занята (символами Stack_Mem и Heap_Mem), но он может разместить эти воспоминания и все глобальные переменные везде, где это необходимо. Метки заканчивают тем, что были символами без длины в данных адресах. __Initial_sp используется непосредственно для таблицы векторов во время соединения, а __heap_base и __heap_limit - вашим кодом времени выполнения. Фактические адреса символов назначаются компоновщиком в зависимости от того, где он их разместил.
Как я упоминал выше, эти символы на самом деле не обязательно должны быть из файла startup.s. Они могут исходить из вашей конфигурации компоновщика (Scatter Load file в Keil, linkerscript в GNU), и в тех, которые вы можете иметь более точный контроль над размещением. Например, вы можете сделать так, чтобы стек находился в начале или конце ОЗУ, или держал глобалы в стороне от кучи или чего угодно. Вы даже можете указать, что HEAP или STACK просто занимают все ОЗУ, оставшиеся после размещения глобалов. Обратите внимание, что вы должны быть осторожны, так как добавление большего количества статических переменных уменьшит вашу другую память.
Однако каждый набор инструментов отличается, и то, как написать файл конфигурации и какие символы будет использовать ваш динамический распределитель памяти, должно исходить из документации вашей конкретной среды.
Размер стека
Что касается определения размера стека, многие цепочки инструментов могут дать вам максимальную глубину стека, анализируя деревья вызовов функций вашей программы, ЕСЛИ вы не используете рекурсию или указатели на функции. Если вы их используете, оценивая размер стека и предварительно заполняя его кардинальными значениями (возможно, с помощью функции ввода перед main), а затем проверяйте после того, как ваша программа некоторое время работала, где была максимальная глубина (где кардинальные значения конец). Если вы полностью исчерпали свою программу до предела, вы будете достаточно точно знать, сможете ли вы уменьшить размер стека или, если ваша программа потерпит крах или не останется кардинальных значений, вам нужно увеличить стек и повторить попытку.
Размер кучи
Определение размера кучи зависит от приложения. Если вы выполняете динамическое распределение только во время запуска, вы можете просто добавить пространство, необходимое в вашем коде запуска (плюс некоторые накладные расходы на управление памятью). Если у вас есть доступ к источнику вашего менеджера памяти, вы можете точно знать, что такое издержки, и, возможно, даже написать код для обхода памяти, чтобы дать вам информацию об использовании. Для приложений, которым требуется динамическая память времени выполнения (например, выделение буферов для входящих кадров Ethernet), лучшее, что я могу предложить, - это тщательно отточить размер стека и дать куче все, что осталось после стека и статики.
Заключительная записка (RTOS)
Вопрос OP был помечен для голого металла, но я хочу добавить примечание для RTOS. Часто (всегда?) Каждой задаче / процессу / потоку (я просто напишу здесь задачу для простоты) будет назначен размер стека при создании задачи, в дополнение к стекам задач, вероятно, будет небольшая ОС стек (используется для прерываний и тому подобное)
Структуры учета задач и стеки должны быть выделены откуда-то, и это часто будет из общего пространства кучи вашего приложения. В этих случаях ваш начальный размер стека часто не имеет значения, потому что ОС будет использовать его только во время инициализации. Я видел, например, что ВСЕ оставшееся пространство во время компоновки должно быть выделено для HEAP и помещено начальный указатель стека в конец кучи для роста в кучу, зная, что ОС будет выделять, начиная с начала кучи, и выделит стек ОС непосредственно перед тем, как покинуть стек initial_sp. Затем все пространство используется для выделения стеков задач и другой динамически выделяемой памяти.