Are there any drawbacks of using static variables?

As others on the forum seem to have, I have discovered the utility of static variables.

The reason for the question is that I've looked over and adapted a not insignificant amount of code at this point (of my novice path) and am not used to seeing static variables, other than static class members, which I've seen a lot of. I don't discount the possibility that I glazed over any other static variables, unaware of their purpose.

Regardless, they have the potential to replace a lot of global variables in my code, as wisdom dictates that less global variables are good.

Going under the assumption that I have not run into many static variables, is there a reason that I should not fill my code up with them? Is there any place where, given the option between the two, global variables are preferred over static ones? Thank you.

Lots of statics are an indication that it's time to transition to object oriented.

Nope. (Given the constraints you've described.)

Nope.

I think it is mainly because the example sketches common in the Arduino world are intended to be as beginner friendly as possible (perhaps followed by the fact that the people who learned from those examples are not familiar with them). The functionality of local scope static variables is not very intuitive compared to a global variable IMO.

It also gets quite confusing once you find that the effect of the static keyword is very different in other usages:

this is probably the route I should take all things considered. I have experience making classes, but need to get better at planning the code ahead of time.

!!! Thanks, this article taught me that it can be used to limit a variable to the scope of a single file, huge!

STATIC variables:
they remain allocated when declared (during compile time - the memory is allocated).
They are like global variables (same behavior): just the keyword static can hide them from each other (same variable names in different modules/files can be used but they are different
memories).

Too many STATIC (plus global) variables can eat up available memory and linker might complain about missing space during compile time (good: happens during compile time).

DYNAMIC variables:
They are allocated on the stack, as really dynamic memory (don't mess up as malloc/free). They are allocated automatically (therefore called AUTO variables) when a function is called.
When the function ends (return) it releases these variables.

So, you can optimize the memory usage by these auto variables (memory becomes reusable for other functions). But bear in mind: the stack size is limited on embedded MCUs (e.g. in total just 2K or ...? - linker script).
So,
huge variables, like character arrays, can try to allocate so much memory that you overflow the stack size during run time!
==> there is not any compile time check - it will crash just during run time only!

And: auto variables (dynamic, stack variables) are stacked up with nested calls: the more nested calls you have - or imagine a recursive call of the same function again - it eats all the stack memory (and can crash during run time).

GLOBAL variables are stored in .data sections (linker script) - fix at compile time and checked

STATIC is stored in .bss sections (linker script) - fix at compile time and checked

dynamic via malloc uses .heap (and is allocated via explicit call during run time) - fix .heap size during compile time only, but allocation during run time - no checks, just crash during run time

AUTO (local dynamic) is stored on stack - total stack size in linker script (top of stack), no checks
on compile time, can crash in a very strange and unpredictable way during run time, the nightmare to find stack violations in code.

STATIC is OK, better to reuse via MEM_Pool (like malloc).

If you have enough memory - static is often preferred by me as well:
Example:
instead of doing:

void function(void) {
    char myHugeArray[64000];          //so much stack memory you will NOT have!

I change to this:

void function(void) {
   static char myHugeArray[64000];   //now I do NOT use stack anymore and 'fine'

But bear in mind:
the second version keeps the myHugeArray allocated, available, all the time (not usable by other functions).
The "beauty" is: next time you enter the same function again - the content of this variable is still alive and still there.
With auto variables (first version) - all is lost on return and on next entry of function all is
unpredicted.

  • auto variables must be initialized before first use

  • static variables are initialized on compile time, they can be used with old, previous content,
    compiler will never complain about "uninitialized"

And due to this fact - bear in mind: STATIC is NOT-Thread-Safe:
if you have RTOS, multi-tasking and different tasks call the same function - the use your STATIC
variable like a shared variable: one task changes, the other will see and use these changes.

It is not thread-safe anymore.
auto (stack) variables are always thread-safe.

Debugger:
often, debuggers "opt-out" to check static variables (when code has left the context).
Even the memory is still there and available, debuggers can be smart and hinder to have a look
at static variables.

Yeah. Not quite right.

But the rest is good.

I like the comment "too many static variables - time for object oriented programming"!

It is really true, cool short answer.
This static is really what happens with classes and objects during runtime:

  • STATIC variables are often just used for large memory where not so much auto/dynamic
    stack variable space is available - my approach to avoid "stack issues": put static in front of large local variables... (ugly)

  • in a semantical point of view: these static variables belong just to one function. Nobody else
    can use and would use (functions in other modules or even other functions in same module
    cannot see and access such a static variable - it depends where defined)
    --> so, these static variables belong really to an "object" (here a function, which is nothing else then a method on an object)

The only real needs for static variables in C-code is:

  • you know your stack size (for auto variables) is to small
    (and you want to avoid stack overflows, crashes due to corrupted stack/auto variables)

  • you need a variable which will survive the return:
    example:
    you want to know how often your function was called - you would do this:

int function(void) {
   static int countMyCalls = 0;     //0 is assigned during compile time, during startup, never again later
   countMyCalls++;
   return countMyCalls;
}

This keeps counting the static variable with every call.
And due to fact this variable is local (static) inside the function - not visible outside the function (for compiler). Therefore we had to return the value here.

static is actually a GLOBAL variable where the use of static - and where it is placed - helps the compiler to deal with the visibility (the scope): which code can see it during compile time.
But during run time - same behavior as any global variable (static is just a keyword for the compiler to manage "access-ability" during compile time, not during runtime).

So Static and Global have the same effect on the heap?

So because the Harvard architecture, we can't easily put string constants like "Hello World" in program space. All those lovely bytes for message strings!

I got around it by storing messages on microSD. Kludge-y but it works. Saved me over 1k of RAM on a MEGA, <50ms to find line 'n' in a 35 line file. Gives power loss retention too. Lots of reads, few writes. Should last forever!

I know Strings chop up the heap, is there any advantage in declaring them in a group at the end instead of logically where they're needed. Put all your INT's, LONGS, etc in consecutive blocks, or does it matter to heap?

When you use static (and global) variables, it requires care if you intend the function to be reentrant. For example, strtok() is not reentrant. You can't process multiple strings with it simultaneously. You must also consider reentrancy if the same frunction can be called from regular code and an ISR.

If you're working with an ESP32, multiple tasks could call the function from multiple cores.

Similar concerns apply if you intend to call the function recursively.

Easily is a matter of opinion, once you get used to how to do it storing string constants in program memory is not difficult.

I don't quite understand that reasoning. The same memory (ram) is used for static variables and stack space, so using a static variable reduces available stack space. Potentially you will even greatly reduce available stack space by unnecessarily declaring variables as static, since that memory is permanently allocated, while using local variable allocated on the stack allows the same pool of memory to be reused for variable in multiple functions.

Globals are a necessary evil but any bug involving one means your search space for debugging is the entire program. Any time you have a chance to use a static or OO stuff to encapsulate something to have smaller scope, take it - each one has the potential to save you debugging time later.

...perpetuated in many of the worked examples :angry:

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.

Great explanation. So is

printf((const char *)"this string remains in flash memory")

what the F() macro does?

printf(F("this string remains in flash memory"))

Also, I got lost in the acronyms here:

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.

No. In fact the casting in the first code does nothing as a string literal passed to a function is already passed as a 'const char *'.

We are obviously thinking of different processors, something like an atmega328 has no such divisions of ram.

But tjaekel said:

But you're saying that:

printf("this string remains in flash memory");

is exactly the same. So WTF does the F() macro do?
I'm coding on MEGA 2560 if that matters.

sorry, true: I was thinking about STM32H7 I am using.

It depends on your linker script. It can place (actually leave) const stuff on R/O ROM (flash). I tried just to illustrate how you can "force" to let stuff in ROM (flash).
You are right, potentially such strings are const already, but such one would not:

char string[] = "hallo";

Even you can do:

print(string);

This is a global variable, write-able, and has to be copied and allocated on RAM.
But if you do:

const char string[] = "hallo";

it can remain only in ROM (flash).
It depends just on linker script where it is placed.
(const is not just a keyword for compiler to check if a write-access happens to it during compile time - it forwards info to linker so that this step can place such code/const-variables in ROM devices).

(sorry, "I love details and correctness" :wink: )

It's easy enough to see what it really does by looking in the source code, rather than just pontificating. The definition of the 'F' macro is in WStrings.h:

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))

And, PSTR is defined in pgmspace.h:

# define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))

So, it's more less the same as:

  static const char c[] PROGMEM = "Hello World";
  Serial.println(reinterpret_cast<const __FlashStringHelper *>(c));

The Print class has an overload to handle __FlashStringHelper * variables in Print.cpp:

size_t Print::print(const __FlashStringHelper *ifsh)
{
  PGM_P p = reinterpret_cast<PGM_P>(ifsh);
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    if (write(c)) n++;
    else break;
  }
  return n;
}

You can continue drilling further for the definitions of PGM_P and pgm_read_byte. But, that's the gist of it.

So if F() is the same as (const char *), as @tjaekel claims, then it's obvious the developers of the Arduino core for AVR processors wasted a lot of effort.