4

This may be a stupid question, sorry.

I've read about the drawbacks of global variables a lot on this site. I'm finally trying to increase my code quality for a large project that'll be reviewed by many. I understand that using global vars makes code harder to debug, since one doesn't know where the variable may be altered (making spaghetti code).

However, I've got a fairly specific case in which I can't really think of a way to avoid global variables. I have a program running on an embedded device (under FreeRTOS, all in C) in which I have about 20 reasonably complex functions, in which there are perhaps 10 global variables passed around. About 3/4 of the code is real-time, and the rest is asynchronous, using the variables the other functions manipulate and sending their contents to a server.

Here's a snippet of pseudocode:

float important_var = 0;

void some_synchronous_function(){
   do_something();
   math.operation(important_var);
}
void do_something(){
   important_var+=5;
}

void async(){
   if(important_var > 5){
       //whatever
   }
}

main(){
    some_synchronous_function();
}

Is this a reasonable way to do this? (cowers in fear)

3
  • @gnat, I read through that, but it doesn't seem to discuss embedded systems or async. programs.
    – 0xDBFB7
    Commented May 24, 2016 at 13:24
  • 4
    One of the reasons classes exist is to make precisely what you are describing possible: have several common functions that share some state. Commented May 24, 2016 at 13:46
  • 5
    "Every rule has an exception; likely including this rule". Global variables are bad; bad; bad ... but you have to be pragmatic too. Embedded systems are a niche area of programming with specific needs around limited resources and the need to prioritise speed & code compactness over readability, ease-of-debugging etc. However, I'd suggest you should always try to write code without them and "degrade" the code if needed (ie avoid premature optimisation by using them from the start)
    – David Arno
    Commented May 24, 2016 at 13:48

3 Answers 3

4

Modifying a variable on one thread and reading it on another is always problematic. Modifying it on two threads is worse.

You can use atomic variables. An atomic variable would for example guarantee that readers get important_var = 0, then important_var = 5.0, then important_var = 10.0 and so on. If important_var is not atomic, reading it just when it is being modified in another thread could produce any result.

Your OS may support atomic operations. The most powerful basically says "change variable x from a to b", which may fail. For example instead of increasing important_var by 5, you would let tmp = important_var, then "change important_var from tmp to tmp + 5". If something changes important_var in between, the operation fails and you try again.

Beyond that you use mutexes, sequential queues, or all kinds of mechanisms that avoid changing a variable while changed from another thread.

1
  • Well, I would argue that load-link/store-conditional is more powerful by far than interlocked compare-exchange. Commented Jun 11, 2016 at 17:06
2

Its not so much that globals make code harder to debug - though if you have a very large program and global state that is defined a long way from where its used, that can have an adverse effect.

The problem with global state is that its global, singular, difficult to extend.

Think of this, you have some variables that you want to modify in your functions, so you've put them somewhere where all functions can share them. Imagine in the future you want to extend your program so that you can run 2 systems side by side. You only have 1 set of global state to share, and so cannot easily fix this. So maybe put all your global state into a single class and share that instead. When you want to extend your program, you can now create 2 instances of that class, one for each running server, making the problem one of deciding which object to use for any given server.

Now you have your global state in more locally held objects, you can even pass those objects to the servers at creation, isolating them further (this is dependancy injection). And now you've got a server that effectively does not use global at all without much change in how the program works.

0

Consider this alternative, which limits the scope of important_var to a single source file and also encapsulates all the important behavior into a c-style class.

important_class.c

static float important_var = 0.0;

void do_important_operation(void)
{
   math.operation(important_var);
}

void do_important_adjustment(float amount)
{
   important_var += amount;
}

bool does_important_condition_exist(void)
{
   return (important_var > 5.0);
}

important_class.h

void do_important_operation(void);
void do_important_adjustment(float amount);
bool does_important_condition_exist(void);

main.c

#include "important_class.h"

void some_synchronous_function(){
   do_something();
   do_important_operation();
}

void do_something(){
   do_important_adjustment(5.0);
}

void async(){
   if(does_important_condition_exist()){
       //whatever
   }
}

main(){
    some_synchronous_function();
}

Your implementation is probably fine for a relatively simple application where important_var is not accessed from too many places. But this class-style implementation is more robust and extensible and will be easier to maintain as you add new features and complexity.

Note that this class-style implementation still needs to be made thread-safe if multiple threads and/or ISRs are calling the important_class methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.