Program space and variables: yes, you can. It is not related to Harvard architecture. If your processor has instructions to read from code memory - it works (and ARM has and can do).
It would be more a question how MPU is configured (e.g. also not allowing code execution from RAM).
STM MCU has flash memory. All the code and data (global as .data and static as .bss) sits there. The startup copies initialized variables from flash to RAM or initializes variables with zero.
It is easy to place strings and other stuff into flash and let it be used directly from flash:
printf((const char *)"this string remains in flash memory");
The compiler/linker should make sure that all stuff defined as const is and remains in flash storage.
"same memory (ram) is used for static variables and stack space" - not really true. Sure, both regions (.data, .bss and stack_area) are on system memory. But we have different memories, e.g. SRAM D1, D2 and D3, also DTCM or external SDRAM.
What is placed where is done by the linker. A linker script will assign memories (which one) is used for what.
Often, I place my stack on DTCM memory:
It is faster memory (no wait state delays). And this DTCM cannot be used as source or destinations for DMAs (e.g. from SPI or ETH). It cannot be used as shared memory, e.g. between M4 and M7. So, it is good for temporary, fast variable access, local variables.
In this case: the stack is on a different memory as global and static.
"so using static variable reduces available stack space" is only true during compile time, not runtime: the linker assigns memory regions for global, static and stack region. This is fix during linking. During runtime the use of global and static does not take anything away from stack memory. Size and location is fix and should never overlap with stack region.
The only thing what can happen is this:
often stack region is defined just by STACK_TOP, the end of the stack, the end of the memory. A dedicated space, let's say 2KB is reserved, remains unused BEFORE the STACK_TOP.
If the code runs and creates local, auto, variables: they go down from STACK_TOP. They use the memory before the STACK_TOP.
And this is not possible to check by linker if a descending stack use would hit the lower end of the stack region. So, large local variables are "stealing" memory from global and static regions. They can hit .data and .bss sections.
Example:
linker script allocates 2 KB for stack region and sets STACK_TOP at the end of this 2 KB block. During runtime you create a local variable which has 3 KB size.
So, down from STACK_TOP the 3 KB before it is now used. This results in fact, that 1 KB is overlapping with another region (the lower 1 KB overlaps with something else, e.g. global and static variables).
If your code writes now to this "oversized stack variable" it overwrites global and static variables. Very bad, very hard to find, very dramatic and random crash effects.
So, actually stack and local, auto, variables "eat" memory, not static and global (they are fixed assigned to memory and fix in size, fix in location etc.)
"reduce available stack space by unnecessarily declaring variables as static": not true, the opposite happens:
if you declare a local variable as static - it is not on stack anymore. You have more stack space available as an effect.
This is what I mean: if you know your stack space is small, you cannot create a large local variable. Instead I use static for such one.
Example:
Let's assume you have this code and the linker reserves 1 KB as stack space.
int myGlobalVar = 10;
void function(void) {
char myLocalVar[1024];
write_to_var(myLocalVar, "1024 charactes");
The linker will allocate memory like this (example):
- place the myGlobalVar in SRAM
- keep 1 KB free (for stack) and set the STACK_TOP 1 KB above the start of "unused"
stack space. The SP register is set to STACK_TOP
- during runtime, when the function() is called, the variable myLocalVar is now located on memory start address as STACK_TOP minus 1 KB.
- this is larger as the reserved stack space. So the start address of myLocalVar is now the same as myGlobalVar. If you write to myLocalVar - you overwrite myGlobalVar.
- the program will crash, at least it will behave strange because myGlobalVar is changed now, in a pretty unpredictable way!
But if I change my code into this:
int myGlobalVar = 10;
void function(void) {
static char myLocalVar[1024];
write_to_var(myLocalVar, "1024 characters...");
This myLocalVar is now NOT on stack region anymore. Instead the linker will place it on similar RAM location like the myGlobalVar.
Now this code is "safe": the too large local variable does not let the stack descending into global variable regions. The overwrite of myGlobalVar does not happen anymore. Potentially the program does not crash anymore.
All is about linker script, memory allocation done during compile time etc. On small systems, embedded systems etc. you should know and often define yourself how the memory allocation looks like, how much space is available for global, static variables and entire stack size (for entire system, for RTOS threads, for nested function calls).
For me: the benefit of STATIC variables is: they do not "eat" stack space. And they are allocated during compile time. Linker makes sure that the size of this variable is really available and allocated. This is not true and not possible for stack variables, for auto variables.
The drawback of STATIC variables is: if never or very seldom used - this memory is not reusable. It lowers your total available memory space. And potentially of too many global and static variables you have to go with compromise: you have to make your stack region smaller. You can run "out of available memory" during compile time.
And: STATIC is not thread-safe, not nice for RTOS threads and tasks.