Spilling the Beans: storage class and linkage in C, including static locals

In Spilling the Beans: C/C++ Header Files, I touched on interfaces and the difference between external and internal linkage. This article has a focus on internal linkage with using the static keyword in C.

Photo by Markus Spiske on Pexels.com

So this might be a programming language refresher, in case you are clear about the difference between declaration and definition, or if you are wondering about internal or external linkage in C.

I focus here on C and do not touch about using ‘static’ in C++ which adds the concept of static classes and objects. Instead let’s focus on the concept of internal linkage.

The C programming language is using the concept of a compilation unit. Basically a compilation unit is what the compiler is compiling in one step: the .c (e.g. module.c) file and everything what it includes with the #include directive.

The compilation unit is a set of ‘objects’ (not in the OOP sense): functions (the code) and the data (variables, constants, etc).

As for data objects, there two different storage types. Each data object occupies memory, it has an address and a size. For the address there are two different kind of storage:

  • static: variables or constants with a fixed/static address. Their address/size remains the same during the lifetime of the program. Usually they are referred as ‘global’ objects or variables.
  • automatic: unlike the static objects, they can change their address. This is used for variables on the stack or function parameters. They are usually referred as ‘local’ objects or variables.

Another concept of the language is the Linkage attribute, either internal or external, which is especially a concern for the linker or linking phase which combines all the compilation units:

  • internal: The object is accessible only inside the compilation unit, and not from outside (from another compilation unit). Because internal objects are only known inside a compilation unit (or module), they are not subject of name collision, as their name is only visible and known inside that module. Internal objects are ‘private’ for the compilation unit.
  • external: The object can be used or referenced from other compilation units. External obects are ‘public’ and visible from the outside. And because C only has a single ‘namespace’, they can cause name collisions, as a program cannot have multiple external objects with the same name.

Last but not last there is the concept of declaration and definition:

  • definition: defines and creates an object with a type and size, and it occupies some amount of memory.
  • declaration: only makes the name and type known to the program, but does not allocate memory

What might be confusing: C has the 4 keywords auto, static, extern and register which are used both for storage class and linkage:

An simple example of a declaration and definition of the same variable, plus a declaration of a function:

extern int i; /* variable declaration, extern mandatory for data declaration */ 
int i; /* variable definition */
extern int calc(int a); /* function declaration, extern optional for function declarations */
  • auto can be used for automatic (local variables), but it is optional and rarely used. Parameters and local variables are auto by default.
  • register is only hint for the compiler to place it in a CPU/MCU register if possible.
static int myGlobalVariable = 0x1234; /* static */
int myOtherGlobal; /* static */

int calc(int a) { /* a is automatic */
  auto int temp = myGlobalVariable; /* automatic, auto keyword is optional */
  register int otherTemp = temp*3; /* another automatic variable */
  return temp*otherTemp;
}

Parameters and variables inside a function are by default auto(matic) and have (no explicit) internal linkage. But for global objects the linkage is important, and here the keywords static an extern are used.

  • static if used for a global object/variable definitions both means internal linkage and static storage class. It is used for both functions and data.
static int i; /* static variable, internal linkage */
static const int k = 5; /* static constant, internal linkage */

static int func(void) { /* static function, internal linkage */
  return 1;
}
  • extern is used to mark external linkage object declarations.
extern int var; /* declaration of a variable with external linkage, extern is mandatory for declaration */
int var; /* definition of variable with external linkage */

void bar(void); /* declaration of function with external linkage */
extern void foo(void); /* declaration of function with external linkage, extern is optional for declaration */
void foo(void) { }  /* definition of function with external linkage */
void bar(void) {} /* definition of function with external linkage */

But there is one more special case: static locals. It means an object inside a function which has static storage and static linkage, and is visible only inside that function scope. It is called ‘static local’ because it looks like an automatic local variable, but is indeed like a static global variable. Below is an example:

void TimerISR(void) {
  static int cntr = 0; /* static local variable */
  cntr++;
  if (cntr==10) {
    LED_Toggle();
  }
}

That ‘cntr’ variable is a ‘static local’, which gets incremented every time the TimerISR function gets executed. It is functionally exactly the same as below.

static int cntr = 0; /* using normal global variable */
void TimerISR(void) {
  cntr++;
  if (cntr==10) {
    LED_Toggle();
  }
}

‘Static locals’ have the advantage that the variable is only visible inside that function, preventing access from other functions. So they are ‘private’ to the function, but otherwise like a normal global variable.

There is just a little problem with static locals: how to see them in the debugger, because they are like global variables, but because of the function scope they might not share a common name?

While inside the function, Eclipse shows it like a normal local variable in the ‘Variables’ view:

Static Local variable during function scope

But otherwise? The linker .map file shows that the variables get a number as suffix:

Static Locals in Linker .map File

The NXP MCUXpresso IDE has a Global Variables View, but this one is not able to list them:

NXP MCUXpresso IDE globals variable view

The workaround is to use the ‘Expressions’ view, and using a ‘functionName’::’variableName’ notation:

Static Local in Expressions View

With this, I can see the static locals like any other global variables :-).

I hope that was a good refresher, just in case :-).

Happy coding 🙂

4 thoughts on “Spilling the Beans: storage class and linkage in C, including static locals

  1. Hi Erich,

    thank you for the nice article. It’s always good to have a clear picture and overview of this topic. What I am wondering if you have ever analysed and compared the assembler code of a static local and a module global variable? The question is – do static locals introduce additional costs? I mean they are differently handled during the first function call compared to the following calls…

    BR,
    Gerhard

    Liked by 1 person

    • Hi Gerhard,
      thanks :-). About the access costs: static locals are accessed exactly the same way as (static or external) global variables, no difference at all. The only difference is visibility from the programming point of view.

      Like

      • Hi Erich,
        sorry, I wasn’t prices enough. I didn’t mean the actual access of the variable.
        However, static locals get initialised at the first call of the corresponding function, but doesn’t at any further calls. Isnt it like that? How is this mechanism handled? Once, a colleague told me that the compiler adds an if clause for every static local. To be honest I’ve never checked the assembler code. Maybe you have and know how this is done. 🙂

        Cheers,
        Gerhard

        Liked by 1 person

        • >>However, static locals get initialised at the first call of the corresponding function, but doesn’t at any further calls. Isnt it like that?
          No, it is not that way. They get initialized like normal global variables at startup time. So special code at all. As I wrote: they are treated and handled exactly like global variables. Only that at compile time they are only accessible and visible from within that function.
          I hope this helps 🙂

          Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.