4
\$\begingroup\$

I'm working on STM32F303VC. How does the heap allocation work?

When I declare the arrays outside main() it allocates them on the heap - SRAM - 0x2000000 and forth in this MCU. But what determines the size of the heap? (And hence its overflow.)

There are two options:

  1. In the 'startup_stm32f30x.s' file I see that:

    Heap_Size       EQU     0x00000200 // Which is only 1K!
    

    [See here: [http://www.keil.com/support/man/docs/armlib/armlib_chr1358938939461.htm. Anyway, I just used the default file from STM's library]

  2. But on the target configurations options, the R/W memory area is declared to be a size of 0x8000 (which is 32K).

So I checked this in the debugger:

uint16_t _xValH[5000];
uint16_t _xValL[5000];
uint16_t _xVal[5000];
//3*5000*2 bytes each = 30 kilobytes

int main(void) {

    _xValH[4999] = 0x4;
    _xValL[4999] = 0x5;
    _xVal[4999] = 0x6;
}

This didn't throw an exception - which means that it is the second option of the above. But furthermore, even if I go above the 32K - say:

uint16_t _xValH[6000];
uint16_t _xValL[6000];
uint16_t _xVal[6000];
//3*6000*2 bytes each = 36 kilobytes

int main(void) {

    _xValH[5999] = 0x4;
    _xValL[5999] = 0x5;
    _xVal[5999] = 0x6;
}

It's still working! The only time it throws me to exception is when I surpass the 40K - which is the limit of the SRAM (0x20009FFF).

So, what is the meaning of the A line code? And the meaning of the size I enter in B?

\$\endgroup\$
  • \$\begingroup\$ If you're looking for a runtime exception, keep in mind that not all microcontrollers have them. For example, they will cheerfully do the math to index past the end of a static array (or before the beginning if your math can include negative numbers), access whatever happens to be there, and go on their merry way. Pointers are similarly followed blindly. No memory protection at all, so everything becomes global. | The only exception you might have of any kind could be a hardware interrupt, for which you've presumably written an interrupt service routine, a.k.a. "exception handler" of sorts. \$\endgroup\$ – AaronD Jun 7 at 3:31
  • \$\begingroup\$ In a context like this (powers of two, hexadecimal numbers), it is better to use 1024 for a kilobyte, not 1000. \$\endgroup\$ – Peter Mortensen Jun 7 at 8:50
  • \$\begingroup\$ @AaronD the fact is I do accept exception when supressing the SRAM limit, as I said. \$\endgroup\$ – Elad Jun 7 at 12:22
  • \$\begingroup\$ @Elad On THAT micro, you do, but you don't always have them in the first place. So don't just blindly rely on it. If you start using lower-end hardware, it'll just blindly go after whatever address comes out of the program. If that address is not implemented, it may wrap around (mod RAM_size) and use that address instead, or the write will do nothing and a read will return 0, -1, maybe the address depending on the hardware implementation, or random garbage. In all cases, the program will carry on as if nothing was wrong. What a way to create weird bugs! (peripherals are in that space too...) \$\endgroup\$ – AaronD Jun 7 at 17:58
  • \$\begingroup\$ @AaronD ok, that is an important note. anyway, can you tell me, if so, what is the meaning of the r/w memory area in the 'target configurations' tool on keil? I can see now on keil's site that "The default check box before each entry enables the area globally for the application" - If on practice I can still allocate area on all SRAM, so it doesn't have any meaning de facto. (?) \$\endgroup\$ – Elad Jun 10 at 7:46
17
\$\begingroup\$

You misunderstand what the heap is.
The heap is the area where malloc gives you blocks of RAM dynamically at run-time.
Your globally scoped, statically allocated variables & arrays are not 'on the heap'.
If you're not using malloc or any of its variants in your program, you can quite safely set the heap size to 0.

\$\endgroup\$
  • \$\begingroup\$ Ok, so this explains that it has nothing to do with the heap. Still, what is the meaning of the r/w memory size declared in the target configurations? \$\endgroup\$ – Elad Jun 7 at 12:17
5
\$\begingroup\$

Those are global variables. Which generally are not allocated on either stack or heap. Exactly where they are is a longer discussion.

Your heap space is for 'globally accessible' variables created during run time. Which is different to global variables, which are allocated before your main() function is entered. Heap space variables can be allocated, de-allocated, reallocated and resized. A global variable you're generally stuck with for the entire program execution.

Your stack space is for local variables. Anything put on it by a function will be removed when the function returns.

While some implementations may put some global variables at the base of the stack, it certainly isn't a rule.

\$\endgroup\$
1
\$\begingroup\$

Consider:

uint16_t _xValH[5000];
uint16_t _xValL[5000];
uint16_t _xVal[5000]; 
//3*5000*2 bytes each = 30Kbytes

int main(void) {

These are global variable declarations. These declarations reserve permanent static space in RAM. The heap is for dynamic allocations in memory.

\$\endgroup\$
0
\$\begingroup\$

The Standard does not require freestanding implementations to provide any sort of heap. In some cases, they will pre-allocate a certain amount of space for use by the malloc() family of functions, with such space being essentially wasted if no such functions are ever used. In other cases, the compiler will reserve a certain amount of RAM for the stack and then make available to malloc() all storage that isn't and won't be used for any other purpose. In still other cases, an implementation won't provide malloc() but will provide the starting and ending address of a range of storage that the implementation has been told the hardware has, but which the implementation itself has no use for; a user application may then subdivide this range of addresses via whatever means it sees fit.

In many cases, the latter approach is the best, because user-written allocation functions can offer finer control over allocations and fallback logic than malloc(). For example, it may be useful to determine, before performing a bunch of allocations, whether all can be guaranteed to succeed (eliminating the need to gracefully recover from an allocation failure in the middle of a task). While malloc() provides no such flexibility, user-written allocation functions can.

\$\endgroup\$
  • \$\begingroup\$ "The Standard" can refer to a very large number of documents. Please say which standard you are referring. \$\endgroup\$ – Stig Hemmer Jun 7 at 7:38
  • \$\begingroup\$ The C89, C90, C99, C11, and C17 standards documents, along with their published final drafts, are all consistent on this issue. \$\endgroup\$ – supercat Jun 7 at 15:18
0
\$\begingroup\$

I hope this should clear things up. It examples the different method of memory allocation in standard C.

#include <stdint.h>
#include <stdlib.h>
#include <stm32f30x.h>

uint32_t static_allocation;

int main(void){
  uint32_t stack_allocation;
  static uint32_t private_static_allocation; 
  uint32_t *heap_allocation;

  heap_allocation = malloc(4);

  static_allocation = 0x11111111;
  stack_allocation = 0x22222222;
  private_static_allocation = 0x33333333;
  *heap_allocation = 0x44444444;

  while(1){
    // Stop simulation here
    __BKPT(0);
    // remove not referenced warnings
    (void)static_allocation;
    (void)stack_allocation;
    (void)private_static_allocation;
    (void)heap_allocation;
  }

  free(heap_allocation);
}

A quick glimpse at symbol table the ARM Linker generously created shows:

private_static_allocation                0x20000004   Data           4  main.o(.data)
static_allocation                        0x20000000   Data           4  main.o(.data)

Which means the linker knows, at compile time, where these variables will live.
Including the following objects in memory map:

0x20000010   0x00000200   Zero   RW            2    HEAP                startup_stm32f30x.o
0x20000210   0x00000400   Zero   RW            1    STACK               startup_stm32f30x.o

And to check it really works like this, simulation debugger shows:

enter image description here

Notice stack_allocation isn't on the actual stack yet, but in a register. This is due to the small amount of variables in this example. Even with -O0 the compiler optimizes stack variables.

Also notice the place of 0x11.., 0x22.. with 0x44.. being inside the heap object.
But no other objects are in here. This means that you can safely reduce heap to 0 if you do not use malloc. Some library functions do implicitly.

Both heap and stack overflow are not warned about during compilation since they are runtime errors.


Relevant documentations: __use_no_heap.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.