Friday, August 8, 2025

The many meanings of C's static

One of the unfortunate side effects of C's insistence on a small number of keywords is keyword overloading where a single keyword has differing semantics depending on where it sits. You can read the standard if you want the precise definitions, but the purpose of this post is to provide a comprehensible guide to new and old C programmers.

Compilation Units 

Before we begin, we need to explain some unforunate jargon: compilation module/unit. This refers to the source file, any #includes, but not anything that the preprocessor removes. Given:

#include "foo.h"
#if 0
int x = 0;
#endif
int main() { return 0; }

The compilation unit contains:

  • the contents of foo.h
  • main function

Generally, you can use the terms source file and compilation unit interchangeably but for precision I will used the term compliation unit. If you're ever confused about what the compilation unit is, use the -E compiler flag.

1. Global variables


static void *memory;

For variables defined outside of a function (file-scope) this acts like a global lowercased variable in Go. It means that this variable is not accessible outside of the compilation unit it appears in.

Always use unless you really intend to share this variable with other source files. It's less necessary for const globals.

2. Functions

 

static void Func() {}

This has the same effect as for global variables in that it hides this function from other compilation unit. It's similar to a lowercase function in Go.

Use for any function you know shouldn't be used outside of the file it appears in e.g. helper functions. The nice thing about doing this is that you can safely change these functions and only need to check usage in the file they're defined in, your users shouldn't be using your statically defined functions!

3. Non-Automatic variables

int Count() {
static int i = 1;
return i++;
}

Full working example: https://godbolt.org/z/6hbxc9srn

This is probably the most 'magical' use of static. Generally, variables declared in functions are automatic (on the stack), they get removed after their scope ends. This form of static lets that data persist. The function Count() above, will return a counter for the number of times it's been called. This may seem magical, but actually it is quite similar to the first example, but instead of this being a global variable to the compilation unit it is global to this function. 

Generally avoid unless this really is the nicest way available to persist some data between function calls.

 

Because of the varying meanings, some people like to use #defines to make the use clear, for example in Handmade Hero, Casey Muratori defines:

#define global_variable static
#define internal        static
#define local_persist   static

Personally, I just find this more confusing since you still need to understand that they're just static under the hood, but to each their own.

 

Further reading

GNU's fairly terse explanations:

I do not recommend cppreference.com. They really overcomplicates things. 

No comments:

Post a Comment