Scope II

In last week's post, we discussed the scope of identifiers in the C language, local, global, and the new loop local variables. This week we will be looking at file local identifiers, how static got co-opted to mean something completely different, and how this all relates to functions.

File Local

Last week I showed the situation where you can easily create one global variable in two files accidentally, potentially causing yourself a lot of issues. To fix a bunch of these issues, C has (the worst named keyword in the WORLD) “static”. The word static does not give the smallest hint as to what it does, so let me explain in the next few examples.

First, when applied to a global variable, it limits the scope to the file.

main.c:

static char buffer[80];
static float notVisibleInUpdateChar;
int main(void) {
    buffer[0] = 'x';
    UpdateFirstChar();
    printf("%c\n", buffer[0]);
}

updatechar.c:

static char buffer[80];
void UpdateFirstChar(void) {
    buffer[0] = 'c';
}

This can be read as buffer_in_main and buffer_in_UpdateFirstChar. These two instances of buffer are different and do not interact. Main can see buffer_in_main, but not buffer_in_UpdateFirstChar. You would get a compiler error for an undefined variable if you try to access notVisibleInUpdateChar from UpdataFirstChar()..

This program prints out the letter x as you might expect.

Static Local Variables

Unfortunately, C also uses the static keyword to limit scope and to change the lifetime of function/loop variables. The visibility of a local variable is just the function/loop, so having a static local variable would not change the visibility. The variable is still only visible to the code in the function.

But, this usage of the static keyword shows another feature of static that is much different. Instead of the function local variable being created when the function is called and destroyed when the function exits, it is created when the program starts and never gets destroyed.

void first(void) {
  static uint32_t i = 1;
...
}

The variable remembers its value between function calls. Call the routine once, set a value. Call it again and the original value is still there. It is like a global variable but only visible to this function.

This can be useful:

1) void process(void) {
2)   static bool isFirstCall=true;
3)   if (isFirstCall) {
4)      doSomethingSpecial();
5)      isFirstCall = false;
6)   }
7)  ...
8) }

On line 1, the variable isFirstCall is initialized to true and this line never gets executed again. The first time process() is called, checking isFirstCall on line 3 succeeds and the initialization code on line 4 runs so isFirstCall is set to false on line 5. Since isFirstCall is static, its value is remembered between calls. The subsequent calls of process() bypass lines 4 and 5 because isFirstCall is false. And since isFirstCall is a local variable, it’s only visible in the process() function, which is the only place it’s needed.

Functions

The visibility of functions is treated in a similar manner as variables. In their unadorned state, they are global and their names cannot be duplicated within a program. If you mark them static, they become file local and can only be used by other functions in that same file. Though valid, reusing function names in multiple files can cause you a heap of hurt when debugging a program.

spi.c :

void SPIInit( void) {
}

static void SPIGetMutex( void) {
}

pressure.c

void PressureInit(void) {
}

static int32_t PressureReadADC( ADC_CHANNEL_T which) {
}

SPIInit() and PressureInit() are visible anywhere in the program, typically to make them available to main(). However, SPIGetMutex() is only visible to other functions in spi.c and PressureReadADC() is only visible to other functions in pressure.c.

Scope refers to the visibility of an identifier in a program. The placement of variable declarations will give loop local, function local, or global visibility. By using the static keyword, we can limit the visibility to file local as well. Limiting scope is a good thing because it avoids unintended identifier reuse and forces correct calling through the published interfaces to functions.


This post is part of a series. Please see the other posts here.


 By Dtjrh2 (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons

By Dtjrh2 (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons