C compiler: Local variables overflowing ram? - Page 1
Forum section: electronics / general

author: DavidAlfa
author_rank: Super Contributor
timestamp: July 03, 2021, 05:08:18 pm
content: I recently added some more variables to the stm32 project and noticed some global variables were overwritten after entering a function that declared a huge local variable.Then I read the C compiler uses stack to alllocate local variables, so there's no way to check them at compile time.Is that correct? So it only worked until now because I was lucky and the memory was not full?A simple example, this compiles perfectly in a stm32F103 with 20KB Ram, although I'm allocating 64KB.Of course, it crashes. I guess the only way for this to work is to use malloc and check a valid pointer?Code:void test(void){volatile uint8_t dat;for(uint32_t t=0;t<sizeof(dat);t++){dat=GPIOA->IDR;}for(uint32_t t=0;t<sizeof(dat);t++){GPIOB->ODR=dat;}}

author: Feynman
author_rank: Regular Contributor
timestamp: July 03, 2021, 05:18:04 pm
content: Local variables are always stored on the stack by default. So it's really a bad idea to declare such huge local variables on an embedded system.On a Cortex-M architecture the stack grows down to lower address and eventually grows into the data section where it corrupts the data.There are several things you can do:- Don't store huge variables on the stack- Estimate the expected maximum stack size and increase its size accordingly- Change the order of memory sections in the linker script so the stack does not "point" towards the data section- ...Apart from that its always a good idea to install proper exception handling in the case of a stack overflow.

author: DavidAlfa
author_rank: Super Contributor
timestamp: July 03, 2021, 05:22:03 pm
content: Yeah, I never cared too much about the stack size. Also rarely used huge local variables.So my approach will be to check the stack usage report, and set the project settings stack allocation a little higher!That should do it, right?But why I don't get any warning? I see free ram 700 bytes, max stack cost 920 bytes (Hence the current problem).Why is nothing checking that, and I must do it manually?Btw it's Stm32 Cube IDE (Would that explain a lot of thing?)

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 03, 2021, 05:26:35 pm
content: Quote from: DavidAlfa on July 03, 2021, 05:08:18 pmYes, local variables are allocated on the stack. That's standard. (Now depending on optimization levels, some local variables may be put in registers, but obviously if they can fit in them. A 64KB array will of course be allocated on the stack.)To answer your question, first you need to know if you really need this array to be dynamically allocated. In small embedded stuff, it's pretty common that statically allocated arrays will work just fine. In particular, if your function is not meant to be re-entrant, you don't need a dynamic allocation. Now the rationale for dynamic allocation would be of course if you need this particular array only temporarily during the program's execution, and would like to be able to use the corresponding RAM for other dynamic allocations the rest of the time.Using malloc() in embedded settings is often questioned, too. So you need to consider if the benefits really overweigh the risks.Another point I would like to stress out regarding checking if the allocation succeeded: for small embedded targets, it's very common that implementations do not handle malloc() failure (in case of not enough RAM available) at all. So you may call malloc() with a large size, the allocation would be impossible, but it will still return a pointer. Often to the beginning of a free block, but the size of which will be much smaller than what you asked for. So you're basically screwed.So, if you really settle on using malloc(), do check on your particular application that calling it with a size that you know won't fit in RAM effectively returns NULL. This is often not guaranteed on base setups for many MCUs. For this to properly work, you often have to write a custom linker script + implement the _sbrk() "system" function. But check it - if you're lucky and failed malloc() returns NULL in your case, then no need to bother; I'm just pointing out it rarely works "out of the box" with the vendor-provided linker scripts and libraries.We had a whole thread about estimating stack usage. Since this is something not trivial (or sometimes impossible depending on code flow) to statically estimate (meaning: at compile and link time), it's pretty usual that no check, and thus no warning, will be issued by the linker if you use up too much stack.

author: DavidAlfa
author_rank: Super Contributor
timestamp: July 03, 2021, 05:40:25 pm
content: I confused myself with the heap size. As I almost never use malloc, I set it close to 0.Done! I have learned something, also feeling a little of idiot for such basic mistakeNow I use the value from "Max cost" in the call graph, is that correct?