Lost at C: The static keyword
The static
keyword in C is very powerful, and has many different uses, depending on the context you
use it in. Let's take a look at some examples:
Static Functions
#include <stdio.h>
static void static_func(void) {
puts("hello from static");
}
void not_static_func(void) {
puts("hello from not static");
}
int main(void) {
static_func();
not_static_func();
}
Compiling/running with gcc file.c && ./a.out
we get the expected output:
hello from static hello from not static
We can take a look at the symbols that are in the a.out
binary using the command nm a.out
:
000000000000039c r __abi_tag 0000000000004030 B __bss_start 0000000000004030 b completed.0 w __cxa_finalize@GLIBC_2.2.5 0000000000004020 D __data_start 0000000000004020 W data_start 0000000000001070 t deregister_tm_clones 00000000000010e0 t __do_global_dtors_aux 0000000000003df0 d __do_global_dtors_aux_fini_array_entry 0000000000004028 D __dso_handle 0000000000003df8 d _DYNAMIC 0000000000004030 D _edata 0000000000004038 B _end 000000000000117c T _fini 0000000000001130 t frame_dummy 0000000000003de8 d __frame_dummy_init_array_entry 0000000000002118 r __FRAME_END__ 0000000000004000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 000000000000202c r __GNU_EH_FRAME_HDR 0000000000001000 T _init 0000000000002000 R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable U __libc_start_main@GLIBC_2.34 0000000000001165 T main 000000000000114f T not_static_func U puts@GLIBC_2.2.5 00000000000010a0 t register_tm_clones 0000000000001040 T _start 0000000000001139 t static_func 0000000000004030 D __TMC_END__
Slimming down to just the parts we care about:
0000000000001165 T main 000000000000114f T not_static_func 0000000000001139 t static_func
We can see t
next to our static function, and T
to our globally accessible functions.
What does that mean? Well, that basically means that we can only access this function from
inside our single compilation unit, in this case, the a.out
binary. If this was an
shared object file (or DLL in Windows-speak) such as libsomething.so
, we would not be
able to call our static_func
function.
This is really good for encapsulation, closing off internals that we don't want people to get their hands on.
Static Data
We can also define "global" variables as static, which will make them local to the single compilation unit they are defined in. As an added bonus, static variables are zero/NULL initialized, so you don't have to give them an initial value!
#include <stdio.h>
static int x;
int main(void) {
printf("x = %i\n", x);
}
Compiling and running with gcc file.c && ./a.out
we get the expected x = 0
output.
Static Variables in Functions
Similar to static variables defined at the module level, we can also define a static variable at the function level. The static variable will still be statically initialized (zeroed out), but will retain it's values across all invocations of the function.
What does that look like?
#include <stdio.h>
void counter(void) {
static int value;
value++;
printf("ran counter %i times\n", value);
}
int main(void) {
counter();
counter();
counter();
}
Compiling and running:
ran counter 1 times ran counter 2 times ran counter 3 times
Here we have a variable called value
, which is statically initialized to zero. Each time we
run the counter
function, it increments value
, and prints it out.
Why would you do this?
Perhaps you have a function which computes a result, and instead of returning it directly, you
return a pointer. Since you don't want to deal with malloc()
, you can just return a pointer to
a statically declared variable. Since the static memory is always present, you will always be able to
read from it, and not have to worry about stack corruptions and such.
What you have to be careful about is using stale versions of static variables, since
they might be updated without you knowing, especially in a threaded environment (if you are using static
in a threaded environment, make sure the caller is aware of any potential side effects!).
Another purpose could be to store the result of a computation one time when the function is first called, and then return the computed value after each successive call:
#include <stdio.h>
unsigned fib(unsigned n) {
if (n <= 2) return 1;
n -= 2;
unsigned first = 1;
unsigned second = 1;
while (n) {
unsigned tmp = second;
second += first;
first = tmp;
n--;
}
return second;
}
unsigned compute(void) {
static unsigned value; // <-- The part we care about
if (!value) {
value = fib(10);
puts("computing");
}
return value;
}
int main(void) {
printf("result: %u\n", compute());
printf("result: %u\n", compute());
printf("result: %u\n", compute());
}
When we compile and run, we get:
computing result: 55 result: 55 result: 55
Fin
Hopefully you got something useful out of this! static
, like most things in C,
can be very powerful if used correctly, or let you write some nasty code if done
incorrectly.