static

So, the static keyword in C is a bit interesting. It actually has 2 different meanings depending on the context it is used. Don't think "static" as in "unchanging", because it's not related to that sense of the word at all!

Outside of functions

When using the static keyword in the declaration of a function, or a global variable, it prevents other files from accessing the function/variable. "But how can other files see the variable or function if we don't declare it in a .h file and #include it?", you may be asking. "Don't we need to do this explicitly for other files to see our functions?". Well, yes and no. We do because we compile with -Wall and -Werror. However, it isn't strictly necessary. The compiler is perfectly happy to see you've referred to some function in file A, then find the definition of it in file B, and say, "Well, this seems about right!". However, you'll get a warning about implicit declarations of functions. With -Werror this escalates to an error, and so the compilation fails.

Basically, functions and global variables are visible to all other files be default. By declaring them static, we're making them inaccessible to all files except the one it's defined in. One place this is useful is if you have two functions with the same name in different files that you're trying to compile together. In this case, the compiler will get confused as it finds two definitions of the same function and isn't sure which one it should be using. Putting static in front of these functions will solve that problem.

Inside a function

When used for a variable inside a function, static means that the variable's value is maintained across different calls to the function, while still remaining in the local scope of the function.

For example:

#include <stdio.h>

void f(){
    static int i = 0;
    
    printf("i is: %d\n", i);
    i++;
}

void g(){
    int i = 0;
    
    printf("i is: %d\n", i);
    i++;
}

int main(){
    printf("Calling f()\n");
    f();
    f();
    f();
    
    printf("\nCalling g()\n");
    g();
    g();
    g();
    
    // printf("i is: %d", i); // The static variable i does not appear in a global context, so this line is invalid
    
    return 0;
}
Output:
Calling f()
i is: 0
i is: 1
i is: 2

Calling g()
i is: 0
i is: 0
i is: 0

The only difference between f() and g() is that f's i variable is declared as static. As such, it retains the incremented value at the end of the function. On the other hand, g's i variable is not static, and so after each function call its value is lost (which is probably what you're used to). Another thing to note is that the line static int i = 0; inside f() is only 'executed' once, in that the value 0 is only assigned to i once, not every call to the function.

If you like, you could think about this use of static as some kind of global variable. Its value is preserved over the lifespan of the entire program, but it is only accessible to the function in which it was declared. If you've heard that global variables are evil, using static in this context is for some of the same reasons -- it makes it difficult to determine the state/value of the variable at any given time in the program.

In terms of what's happening in system memory, non-static variables are stored on the stack; an area of memory which is given to different functions as they called, and when the function ends that memory is available to be give to another function. A static variable is stored at a low memory address; below the heap even (which is the memory accessed when you perform a malloc()). This memory isn't affected between function calls, allowing the value of a variable stored here to be retained.