Jump to content

    

Выделение места под стек и кучу в Си

Как в Си-файле выделить место под стек и кучу? Компилятор ARM 5.06 (среда Keil MDK ARM 5.24).


Имеется стандартный ассемблерный файл Startup.s из CMSIS.

Spoiler

;<h> Stack Configuration
;  <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;</h>

Stack_Size      EQU      0x00000400

                AREA     STACK, NOINIT, READWRITE, ALIGN=3
__stack_limit
Stack_Mem       SPACE    Stack_Size
__initial_sp


;<h> Heap Configuration
;  <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;</h>

Heap_Size       EQU      0x00000C00

                IF       Heap_Size != 0                      ; Heap is provided
                AREA     HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE    Heap_Size
__heap_limit
                ENDIF

 

Показан фрагмент, где резервируется память под стек и кучу.

Хочу сделать его аналог на Си.
Как объявлять таблицу векторов прерываний много где описано - в любой теме про загрузчики есть эта информация. А вот как выделить (зарезервировать) память под стек и кучу нигде не находил.
Предлагаю здесь обсудить этот вопрос. 
Изначально я хотел сделать так: объявить два массива байт с выравниванием по границе 8 байт: один под стек, второй под кучу.
Размеры задать константами, объявленными, как макросы через #define. Адрес конца массива под стек занести в начало таблицы векторов прерываний.
Встал вопрос: а нужно ли как-то сообщить компилятору/линкеру о размещении и размерах стека и кучи? Оказалось, что да, надо. Здесь пишут, что надо определить имена: __initial_sp, __heap_base, __heap_limit.
Вот по их использованию есть несколько вопросов:
1. В примере по ссылке в ассемблерной вставке эти имена только определяются. Резервирование памяти в явном виде, как в ассемблерном стартапе, не происходит.
Вопрос: почему? Ведь в ассемблерном стартапе память под стек и кучу явно выделяется директивой SPACE.
2. Почему их надо определять именно в ассемблерном коде? Почему нельзя определить в Си-коде через #define? Зачем для этого городить ассемблерные вставки?

Share this post


Link to post
Share on other sites
40 minutes ago, Darth Vader said:

память под стек и кучу нигде не находил.

Этим линкер занимается, читайте информацию про него.

 

Share this post


Link to post
Share on other sites
1 hour ago, Darth Vader said:

Хочу сделать его аналог на Си.

А в чем смысл, можете пояснить?

 

В "C" можно было только __user_initial_stackheap() использовать.

Share this post


Link to post
Share on other sites

Как выше было сказано, место резервируется не в стартапе, а в линкере. В стартапе лишь берут данные из линкера и затем инициализируют все необходимое. Я утащил сишный стартап из opencm3. Там прекрасно видно вот это:

extern unsigned _data_loadaddr, _data, _edata, _ebss, _stack;
extern funcp_t __preinit_array_start, __preinit_array_end;
extern funcp_t __init_array_start, __init_array_end;
extern funcp_t __fini_array_start, __fini_array_end;

сами адреса вычисляются линкером из ld-скрипта.

А уж в линкере можно и свои секции объявлять - хоть во флеше, хоть в памяти... Я себе во флеш две секции добавил, чтобы иметь область для сохранения конфигурационных данных и логов.

Share this post


Link to post
Share on other sites
2 hours ago, Darth Vader said:

 Оказалось, что да, надо. Здесь пишут, что надо определить имена: __initial_sp, __heap_base, __heap_limit.

Заметьте, там все время повторяется будете или нет вы использовать стандартные библиотеки Keil и какие. 
Некоторые опенсорсные проекты и большинство RTOS игнорят эти библиотеки, так что в приложении от штатной установки стека и кучи будет ни холодно, ни жарко. 
Поэтому разумнее их назначить не через линкер, а через объявление массивов и переопределение специальных функций, как советуется по вашей ссылке. 
Потом эти массивы можно переиспользовать в настоящей куче  назначаемой в  приложении. 

Share this post


Link to post
Share on other sites
17 minutes ago, AlexandrY said:

Поэтому разумнее их назначить не через линкер, а через объявление массивов и переопределение специальных функций, как советуется по вашей ссылке. 
Потом эти массивы можно переиспользовать в настоящей куче  назначаемой в  приложении.

Назначение секций линкером никак не помешает использовать их повторно в "настоящей" куче.

Share this post


Link to post
Share on other sites
8 minutes ago, aaarrr said:

Назначение секций линкером никак не помешает использовать их повторно в "настоящей" куче.

Речь не про то что в принципе помешает или нет, а про более эффективные практики.
По ссылке не менее 3-х методов, но какой наиболее полезный? 

Share this post


Link to post
Share on other sites
2 hours ago, AlexandrY said:

По ссылке не менее 3-х методов, но какой наиболее полезный?

Ну так расскажите, а мы послушаем. Как еще обучиться эффективным практикам-то :lol2:

Share this post


Link to post
Share on other sites

Определить и разместить стек и кучу в С-ном файле несложно. Но у стандартных библиотек есть вызов функции __user_initial_stackheap() в которой стартап должен вернуть в регистрах R0,R1,R2,R3 размер и расположение стека и кучи. Как это сделать без асм я что-то не допёр. Прикладываю мои упражнения по созданию кейловского стартапа на Си.

STM32F411VE-SRAM.sct

startup_stm32f411xe.c

Share this post


Link to post
Share on other sites
5 часов назад, Eddy_Em сказал:

Там прекрасно видно вот это

Да кто ж тебя про GCC спрашивал то?

Share this post


Link to post
Share on other sites
1 hour ago, VladislavS said:

Определить и разместить стек и кучу в С-ном файле несложно. Но у стандартных библиотек есть вызов функции __user_initial_stackheap() в которой стартап должен вернуть в регистрах R0,R1,R2,R3 размер и расположение стека и кучи. Как это сделать без асм я что-то не допёр.

using StackItem = uint64_t;
using StackSize = uint32_t;
using HeapItem = uint64_t;

struct InitialStackHeapConfig
{
	unsigned heapBase; 		// low-address end of initial heap
	unsigned stackBase; 	// high-address end of initial stack
	unsigned heapLimit; 	// high-address end of initial heap
	unsigned stackLimit;	// unused
};
#define DEFAULT_HEAP_SIZE_BYTES 		(4096)
#define STARTUP_STACK_SIZE_BYTES 		(512)

__attribute__((used))
__attribute__((section(".stack"))) 
__attribute__((aligned(sizeof(StackItem))))
static StackItem  startupStack[STARTUP_STACK_SIZE_BYTES / sizeof(StackItem)] ;

__attribute__((used))
__attribute__((section(".heap")))
__attribute__((aligned(sizeof(HeapItem))))
static HeapItem defaultHeap[DEFAULT_HEAP_SIZE_BYTES / sizeof(HeapItem)];
extern "C" __attribute__((value_in_regs)) struct InitialStackHeapConfig
__user_setup_stackheap(unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
   struct InitialStackHeapConfig config;

	config.heapBase = reinterpret_cast<unsigned int>(defaultHeap);
	config.stackBase = reinterpret_cast<unsigned int>(startupStack) + STARTUP_STACK_SIZE_BYTES;
	config.heapLimit = reinterpret_cast<unsigned int>(defaultHeap) + DEFAULT_HEAP_SIZE_BYTES;
	config.stackLimit = reinterpret_cast<unsigned int>(startupStack); // unused

	return config;
}

 

Share this post


Link to post
Share on other sites

Спасибо, будем знать. Вся хитрость в нестандартном 

__attribute__((value_in_regs))

А не подскажешь как надёжно узнать, что тебя из-под Keil компилят, а то эта редиска на __GNUC__ отзывается.

Share this post


Link to post
Share on other sites
2 minutes ago, VladislavS said:

А не подскажешь как надёжно узнать, что тебя из-под Keil компилят, а то эта редиска на __GNUC__ отзывается.

Не знаю, но я бы начал с гугля, потом мануала на компилятор, на край порылся бы в исходниках стандартных библиотек под этот компилятор.

 

4 minutes ago, VladislavS said:

Вся хитрость в нестандартном

Это все есть в мануале, оттуда и заимствовано.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now