Scope I

Identifiers are the names of functions, types, variables and defines in your code. The compiler requires that all of these identifiers are unique so it can do what you expect. But does that mean that your whole program can only have one variable named “i”? Not really, since that is a bit unreasonable, let me introduce you to the concept of scope.

Scope defines the visibility of identifiers in your code, which parts of your program can see and use the identifier. An identifier can be scope-limited so that it is only in scope or visible within a loop, a function, or a file. In other loops, functions, or files those names are invisible so they can be reused.

You may have heard of local variables and the dreaded global variables. These are examples of what I’m talking about: local and global variables have different scope. Local variables can only be seen within the function (or loop) where they are declared; global variables can be seen anywhere in your program.

For example, if we have two functions named first and second, each can have a local variable called “i”.

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

void second(void) {
  uint16_t i;
  i = 2;
}

These two instances of “i” are distinct because they were declared inside of the functions. It is analogous to one being called i_in_first and the other being called i_in_second.

You can also declare variables outside of a function body.

char buffer[80];
void third(void) {
   buffer[0] = ‘*’;
}

void fourth(void) {
   buffer[0] = ‘c’;
}

This notation, declared outside of any function body without any other keywords, gives you a global variable. Our character array “buffer” is available for use in all four functions (first, second, third, and fourth). Third and fourth have no access to either of the variables named “i”.

Multiple Files

A C program can be made up of multiple files of code. This is a powerful method of breaking a program down into understandable chunks. Typically a file contains all of the functions to do one, and only one, particular task, say, handle the details of an SPI bus or a pressure sensor.

Since a file contains all of the functions needed to do a task, functions might need to share data with other functions. The buffer from the previous example almost suits our purpose but it has a fatal flaw: it’s a global variable, meaning it’s visible to all of the functions in the program, even those in other files.

The flaw is quite common. In this example main places the character "x" into its array called buffer then calls UpdateFirstChar. UpdateFirstChar places the character "c" into its array called buffer and returns. Finally main prints out the character.

main.c:

char buffer[80];

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

updatechar.c:

char buffer[80];

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

The results of compiling and linking these two files together may vary. With the IAR and Xcode compilers, this gets flagged as an error by the linker because of buffer being a duplicate symbol.

GCC does not generate an error or even a warning. When this compiles, you might think that this would print an “x”, but it doesn’t, it prints a “c”. Even though two files are being used and each has a declaration of buffer, GCC is smart enough to figure out that the global variable buffer in main.c is the same as global variable buffer in updatechar.c.

This action happens even if the sizes and/or types of the arrays are different. The variable buffer in updatechar.c could be declared as an array of 100 floating point number and can cause some nasty buffer overflows in main.c.

Think carefully before using global variables. When I write code, I start with a code template that has places for my includes, defines, global variables and others. The section for globals looks like this:

/*
 * Global variable section - Think very carefully before using this.
 */

I cannot recall using this section except when I needed to hook in someone else’s library code.

Since globals are visible anywhere, and if you really must use them, it is a good idea to preface your names with the name of the module where they are defined, like SPI_Buffer and UART_Buffer. That way you avoid conflicts with your own code as well as someone else’s library code where they might have a global called buffer as well.

Fine Grained Scope

C99 introduced the concept of declarations within a compound statement. What does that mean? It means that you can have variables that are created when a loop starts and gets disposed of when the loop exits. I like to call them loop locals.

for (uint8_t i = 0; i < 42; i++) {
    DontPanic();
}

Our loop control variable “i” only exists in the for loop. Once the for loop completes, "i" is no longer visible. You would get a compiler error if you tried to use the “i” variable outside of the for () loop.

If you have the (bad) habit of using GOTO statements, you can cause the loop locals to not get released when you jump out of a block of code. Since the block didn’t exit normally, the memory did not get released (popped off of the stack). Now you have a memory leak. Good luck.

 

Next week we will be looking at file scope, function scope, and the static keyword. This will give you the information you need to just say no to global variables.


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