NSWI170 Computer Systems

Guidelines to write a better C/C++ code

previous curse | all curses | next curse

Global variables present a genuine danger...

Unforgivable curse #4: Global variables

Global variables often present an easy way how to hold a state that is assumed application-wise, so it can be accessed from every function. However, modern applications rarely use global variables since nothing is really application-wise and some level of encapsulation is usually advisable. In the Arduino projects, utilization of global variables will become a necessity since the interface is somewhat cringe. More advanced programmers may replace global variables with static variables, but that is quite beyond the scope of this text.

Motivation

The motivation is perhaps best illustrated by the associated meme on the right. Once something is global, it may be changed from anywhere and that could be hazardous and error-prone since it is difficult to track, where the variable is being used. Global variables also complicate code reusability as any function that uses a global variable cannot be simply used in another project unless the global variable is copied as well. Finally, in C/C++, the global variables are even more dangerous since they can be used in functions without explicit declarations, so it is easy to mistake a global variable for a local variable.

Explanation

In regular C/C++ applications, global variables are often avoided altogether. Data that needs to be shared across functions or modules are wrapped into structures or objects, allocated dynamically, and managed by smart pointers. Even without objects, we could make the variables local in main() (unless they are large and require dynamic allocation) and pass them down as needed into called functions.

In Arduino code, this is not possible due to the interface which comprises setup() and loop(). Since the loop() function is called repeatedly and should not block, the application state needs to be stored in global variables. However, we place some restrictions on the use of global variables:

There is a quick test that would indicate whether a global variable in your code should be removed (replaced): If you could change the value of that variable in between two subsequent invocations of loop() and it will not affect the behavior of your application, then the global variable should not be there.

Another good piece of advice is to minimize the number of global variables to make the situation more manageable. One way how to do that is to aggregate related values into structures/classes and then make one global instance of that structure.

Example

The example is a bit artificial, but it demonstrates one of the most typical errors. The functions initialize() and update() are more universal when the state is passed in via an argument. When the state needs to be modified, it is passed via reference (&). Subsequently, the variable (var) could be either made local (in main()) or accessed only from callers of initialize() and update() (which might be setup() and loop() in the case of Arduino).

💩 Bad code 👍 Good code
int var;

void initialize() {
    var = 54;
}

void update() {
    var += 2;
}

int main() {
    initialize();
    update();
    print(var);
}
void initialize(int &var) {
    var = 54;
}

void update(int &var) {
    var += 2;
}

// another possible version could be (depends on situation)
int update2(int var) {
    return var + 2;
}

int main() {
    int var;
    initialize(var);
    update(var);
    print(var);
}