Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
What does the static keyword do in C?
What exactly does the static
keyword do in C? I have seen it used in several diverse contexts:
1) As a variable outside a function:
#include <stdio.h>
static int x = 5;
int main (void)
{
printf("%d\n", x);
}
2) As a variable inside a function:
void func (void)
{
static int count=0;
printf("%d\n", count++);
}
3) As part of a function declaration/definition:
static void func (void);
4) As part of a function parameter list containing an array:
void func (int arr[static 5]);
All of these uses seem quite unrelated. What exactly is the purpose of static
in all the above examples?
2 answers
static
is an unfortunately heavily overloaded keyword.
At "file scope"
Symbols are said to be at "file scope" if their definition is not inside a function.
When the static
keyword appears at file scope, it specifies the linkage of the symbol to which it is applied. "Linkage" in this case means "visibility outside the current translation unit." So a variable name or a function name that are not declared static
will default to being visible to outside translation units that may want to link to them ("reference" them, in linker terms). But a symbol marked static
is not visible outside the current translation unit. So at file scope, static
means private
.
At "function scope"
When the static
keyword appears at function scope -- that is, when it applies to a symbol defined inside a function -- it specifies the storage duration of the symbol to which it is applied. You will know that with the exception of GCC, which provides an extension for the purpose, functions cannot be defined inside other functions. So static
at function scope will be applied only to variables.
By default, variables at function scope have automatic storage duration. This means they are created on the stack, and cease to exist when control leaves the scope of the function. (They are said to be "on the stack" because most CPUs support quickly allocating storage on the stack for this purpose.)
But variables inside a function that are declared static
become persistent. Their storage duration is "static," meaning that the value is not stored on the stack. It is stored in the global data area, defaults to 0, is initialized before main
is called, etc. All the things that are true of global (file scope) variables are also true of static
variables at function scope. Most importantly, static
variables in a function hold their value from one call to the next.
At "parameter scope"
The use of the static
keyword for parameters is just "we need a keyword, and don't want to introduce a new one." It basically says "the pointer being passed is not null, and points to at least this much storage." It has no relation to the other meanings of static
.
Storage class specifiers
static
is strictly speaking a storage class specifier, which is an attribute given to a variable or a function stating how it is allocated, how it is accessible and how long it will be valid. Other examples of storage class specifiers are extern
and register
.
It should be noted that a storage class specifier should always be written to the far left in a declaration, doing otherwise is obsolete syntax. From C17 6.11.5: "The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature."
The static
keyword is however also used in a few other contexts too, which will be addressed below.
Scope and linkage
If a static
variable or function is declared at file scope - outside any function body - it ensures that the variable or function is only accessible from that .c file and any header included by that file: from within the same translation unit where it was declared.
This is called internal linkage. A variable or function with internal linkage may not be referred to by other files and cannot be accessed as a "global" variable with extern
. It is a way to give the variable private encapsulation, to reduce access and scope. It's generally considered good practice.
In example 1), x
is only accessible by the .c file where it was declared and not by other .c files. This reduces namespace conflicts and clutter, but more importantly also prevents other parts of the program to use the variable by accident or intentionally.
Similarly in example 3), func
cannot be accessed by other .c files. A function declared as static
should be kept local to the .c file and not exposed through a header file.
Storage duration
Storage duration specifies the lifetime of a variable. If declared as static
, it has static storage duration. This means that the variable is valid and persists throughout the whole execution of a program.
Variables with static storage duration have special rules of initialization. They will get initialized before main()
is called and the program is started and not when the code line containing the declaration is encountered. Meaning that in example 1) the code x = 5
is executed before main()
is called. And in example 2) the code count=0
is also executed before main() is called. This means that the initialization code is only executed once and not each time a function is entered, as would be the case with local variables.
Since the variables are initialized before main()
is called, it also means that the initializer list must only contain compile-time constants, since it is always resolved at compile-time. So we can't initialize a static
variable to the result of another variable or a function call etc.
Furthermore, all variables with static storage duration are guaranteed to be initialized. These are the only variables in C with such a guarantee. If the programmer does not write initialization code explicitly, then they will get initialized to zero implicitly. (static
pointers will get initialized as null pointers.) So in example 2) the code count=0
is strictly speaking superfluous - the variable would have been set to zero anyway. Though it is good practice not to rely on implicit zero but to write the initializer explicitly.
Memory allocation
Variables with static storage duration also get allocated at special places in memory and not on the stack as local variables. The C standard doesn't mandate where they are allocated, but in practice all C implementations create two segments for them, normally referred to as .data
and .bss
("block starting symbol", a confusing name with lots of history behind it). This is for example standardized by the ELF file format. All static storage variables that were initialized explicitly to a non-zero value get stored in .data
. All other static storage variables that were initialized to zero, explicitly or implicitly, get stored in .bss
.
Before main()
is called, the "C run-time" start-up code executes initialization of .data
and also sets everything in .bss
to zero. This is the reason why they are kept as two different segments: setting a whole chunk of variables to zero in one go is faster.
Static inside function parameter array declarations
Disclaimer: this part of the answer is an advanced topic and only included here for completeness. The vast majority of C programmers never use these features.
With the C99 version of the C standard, various special features for declaring arrays as parameters to a function were introduced. One such feature enables us to specify that an array parameter should at least have a certain size and that it cannot be a null pointer.
In example 4) void func (int arr[static 5]);
means that arr
must at least have size 5 and it cannot be a null pointer.
Historically, compilers haven't really supported this this feature until quite recently. Some modern compilers have implemented diagnostics for it. Here is an example:
void func (int arr[static 5])
{}
int main(void)
{
int arr[4]={1,2,3,4}; // too small array
func(arr);
func(NULL); // null pointer
return 0;
}
For the first error, gcc 11.2 gives:
warning: 'func' accessing 20 bytes in a region of size 16 [-Wstringop-overflow=]
clang 13.0.0 gives:
warning: array argument is too small; contains 4 elements, callee requires at least 5 [-Warray-bounds]
For the null pointer error, we have to do gcc -Wall
in order to get:
warning: argument 1 to 'int[static 20]' is null where non-null expected [-Wnonnull]
clang 13.0.0 always gives a warning:
warning: null passed to a callee that requires a non-null argument [-Wnonnull]
1 comment thread