20

I started writing firmware for my product and I'm a rookie here. I went through many articles about not using global variables or functions. Is there any limit for using global variables in an 8 bit system or is it a complete 'No-No'. How should I use global variables in my system or should I completely avoid them?

I would like to take valuable advice from you guys on this topic to make my firmware more compact.

Olin Lathrop
  • 310,974
  • 36
  • 428
  • 915
Rookie91
  • 2,108
  • 4
  • 29
  • 46
  • This question isn't unique to embedded systems. A [duplicate can be found here](http://stackoverflow.com/questions/484635/are-global-variables-bad). – Lundin Jan 29 '14 at 14:26
  • @Lundin From your link: "These days that only matters in embedded environments where memory is quite limited. Something to know before you assume that embedded is the same as other environments and assume the programming rules are the same across the board." – endolith Feb 02 '18 at 20:07
  • @endolith `static` file scope is not the same thing as "global", see my answer below. – Lundin Feb 05 '18 at 08:00

6 Answers6

35

You can use global variables successfully, as long as you keep in mind @Phil's guidelines. However, here are some nice ways to avoid their issues without making the compiled code less compact.

  1. Use local static variables for persistent state that you only want to access inside one function.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
    
  2. Use a struct to keep related variables together, to make it clearer where they should be used and where not.

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
    
  3. Use global static variables to make the variables visible only within the current C file. This prevents accidental access by code in other files due to naming conflicts.

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */
    

As a final note, if you are modifying a global variable within an interrupt routine and reading it elsewhere:

  • Mark the variable volatile.
  • Make sure it is atomic for the CPU (i.e. 8-bit for an 8-bit CPU).

OR

  • Use a locking mechanism to protect access to the variable.
richarddonkin
  • 646
  • 4
  • 5
  • volatile and/or atomic vars are not going to help you avoid errors, you need some sort of lock/semaphore, or to briefly mask off interrupts when writing to the variable. – John U Jan 22 '14 at 10:26
  • Volatile atomic variables work perfectly in 8-bit CPUS, if they are only written in one context and only read in other contexts. Only having multiple writers requires you to use a locking mechanism or disable interrupts. Locking is usually required, however, because the relevant data is more than can fit in an atomic variable access. – richarddonkin Jan 22 '14 at 10:36
  • 3
    That's quite a narrow definition of "work fine". My point was that declaring something volatile does not prevent conflicts. Also, your 3rd example is not a great idea - having two *separate* globals with the *same name* is at the very least making the code harder to understand / maintain. – John U Jan 22 '14 at 10:47
  • @richarddonkin Thanks a lot .But i have one doubt regarding point 3.Will the two global static variables treated as separate if we initialize them in two separate source files in the same project. Ohk then the compiler must treat source files as modules and treat such kind of things separately right?.Correct me if i am wrong. – Rookie91 Jan 23 '14 at 03:01
  • @Rookie91 Yes, the compiler treats each C source file as a compilation "module", and global static variables are contained within their respective "modules". But this is only true for _static_ global variables. Also, as @John commented, you usually don't try to name different global variables with the same name; it happens by mistake. Marking them `static` prevents the compiler from getting them confused when you make this mistake. – richarddonkin Jan 23 '14 at 06:40
  • @richarddonkin Thank you very much for making me understand it well . – Rookie91 Jan 23 '14 at 06:51
  • You're welcome. (Please mark the reply as the answer if you think it's the answer to your question.) – richarddonkin Jan 23 '14 at 07:38
  • 1
    @JohnU You shouldn't use volatile to prevent from race conditions, indeed that won't help. You should use volatile for preventing dangerous compiler optimization bugs common in embedded systems compilers. – Lundin Jan 29 '14 at 13:01
  • 2
    @JohnU: The normal use of `volatile` variables is to allow code running in one execution context to let code in another execution context know something has happened. On an 8-bit system, a buffer which is going to hold a power-of-two number of bytes no greater than 128 can be managed with one volatile byte indicating the total lifetime number of bytes put into the buffer (mod 256) and another indicating the lifetime number of bytes taken out, provided that only one execution context puts data into the buffer and only one takes data out of it. – supercat Jan 29 '14 at 19:02
  • 2
    @JohnU: While it might be possible to use some form of locking or temporarily disable interrupts to manage the buffer, it really isn't necessary or helpful. If the buffer had to hold 128-255 bytes, coding would have to change slightly, and if it had to hold more than that, disabling interrupts would likely be necessary, but on an 8-bit system buffers are apt to be small; systems with bigger buffers can generally do atomic writes larger than 8 bits. – supercat Jan 29 '14 at 19:06
26

The reasons you would not want to use global variables in an 8-bit system are the same you would not want to use them in any other system: they make reasoning about the program's behavior difficult.

Only bad programmers get hung up on rules like "don't use global variables". Good programmers understand the reason behind the rules, then treat the rules more like guidelines.

Is your program easy to understand? Is its behavior predictable? Is it easy to modify parts of it without breaking other parts? If the answer to each of these questions is yes, then you are on the way to a good program.

Phil Frost
  • 56,804
  • 17
  • 141
  • 262
  • 1
    What @MichaelKaras said - understanding what these things mean and how they affect things (and how they can bite you) is the important thing. – John U Jan 22 '14 at 10:50
  • Might not be a bad programmer, might be a programmer who has restrictions from outside entities (e.g. governments and other regulatory authorities) that require not using any global variables for the project they are on... and certainly you always want to MINIMIZE global variables and only use them when necessary – Trashman May 11 '22 at 15:25
6

You shouldn't completely avoid using global variables ("globals" for short). But, you should use them judiciously. The practical problems with excessive use of globals:

  • Globals are visible throughout the compilation unit. Any code in the compilation unit can modify a global. The consequences of a modification can surface anywhere where this global is evaluated.
  • As a result globals make the code harder to read and understand. The programmer always has to keep in mind all of the places where the global is evaluated and assigned.
  • Excessive use of globals makes the code more defect-prone.

It's a good practice to add a prefix g_ to the name of global variables. For example, g_iFlags. When you see the variable with the prefix in the code, you immediately recognize that it's a global.

Nick Alexeev
  • 37,739
  • 17
  • 97
  • 230
  • 2
    The flag *doesn't* have to be a global. The ISR could call a function that has a static variable, for example. – Phil Frost Jan 22 '14 at 03:46
  • +1 I haven't heard about such trick before. I've removed that paragraph from the answer. How would the the `static` flag become visible to the `main()` ? Are you implying that the same function that has the `static` can return it to the `main()` later? – Nick Alexeev Jan 22 '14 at 03:55
  • That's one way to do it. Perhaps the function takes the new state to set, and returns the old state. There are plenty of other ways. Maybe you have one source file with a function to set the flag, and another to get it, with a static global variable holding the flag state. Though technically this is a "global" by C terminology, its scope is limited to just that file, which contains only the functions that need to know. That is, it's scope is not "global", which is really the problem. C++ provides additional encapsulation mechanisms. – Phil Frost Jan 22 '14 at 04:01
  • 4
    Some methods of avoiding globals (such as accessing hardware only through device drivers) may be too inefficient for a severely resource-starved 8-bit environment. – Spehro Pefhany Jan 22 '14 at 04:07
  • 1
    @SpehroPefhany *Good programmers understand the reason behind the rules, then treat the rules more like guidelines.* When the guidelines are in conflict, the good programer weighs the balance carefully. – Phil Frost Jan 22 '14 at 04:10
4

The advantage of global data structures in embedded work is that they are static. If every variable you need is global, then you will never accidentally run out of memory when functions are entered and space is made for them on the stack. But then, at that point why have functions? Why not one big function that handles all the logic and processes - like a BASIC program with no GOSUB allowed. If you take this idea far enough you will have a typical assembly language program from the 1970's. Efficient, and impossible to maintain and trouble shoot.

So use globals judiciously, like state variables (for instance, if every function needs to know if the system is in interpret or run state) and other data structures that need to be seen by many functions and as @PhilFrost says, is the behavior of your functions predictable? Is there a possibility to fill the stack with an input string that never ends? These are matters for algorithm design.

Note that static has different meaning inside and outside a function. https://stackoverflow.com/questions/5868947/difference-between-static-variable-inside-and-outside-of-a-function

https://stackoverflow.com/questions/5033627/static-variable-inside-of-a-function-in-c

C. Towne Springer
  • 2,141
  • 11
  • 14
  • 2
    Many embedded-systems compilers allocate automatic variables statically, but overlay variables used by functions which cannot be in scope simultaneously; this generally yields memory usage which is equal to the worst-case-possible usage for a stack-based system in which all statically-possible call sequences may in fact occur. – supercat Jan 22 '14 at 18:01
4

Global variables should only be used for truly global state. Using a global variable to represent something like e.g. the latitude of the northern boundary of the map will only work if there can only ever be one "northern boundary of the map". If in future the code might have to work with multiple maps that have different northern boundaries, code which uses a global variable for the northern boundary will likely need to be reworked.

In typical computer applications, there's often no particular reason to assume there will never be more than one of something. In embedded systems, however, such assumptions are often far more reasonable. While it's possible that a typical computer program might be called upon to support multiple simultaneous users, the user interface of a typical embedded system will be designed for operation by a single user interacting with its buttons and display. As such, it will at any moment in time have a single user-interface state. Designing the system so that multiple users could interact with multiple keyboards and displays would require a lot more complexity, and take much longer to implement, than designing it for a single user. If the system is never called upon to support multiple users, any extra effort invested to facilitate such usage will be wasted. Unless it's likely that multi-user support will be required, it would likely be wiser to risk having to discard the code used for a single-user interface in the event that multi-user support is required, than to spend extra time adding multi-user support which will likely never be needed.

A related factor with embedded systems is that in many cases (especially involving user interfaces), the only practical way to support having more than one of something would be to use multiple threads. In the absence of some other need for multi-threading, it's likely better to use a simple single-threaded design than increase the system complexity with multi-threading that is likely never to really be necessary. If adding more than one of something would require a huge system redesign anyway, it won't matter if it also requires reworking the use of some global variables.

supercat
  • 45,939
  • 2
  • 84
  • 143
  • So keeping global variables that won't clash with each other won't be a problem right. eg: Day_cntr , week_cntr etc for counting days and week respectively.And I trust one should not deliberately use much of global variables that resemble same purpose and must clearly define those.Thanks a lot for the overwhelming response.:) – Rookie91 Jan 23 '14 at 04:40
-2

Lots of people are confused about this subject. The definition of a global variable is:

Something which is accessible from everywhere in your program.

This is not the same thing as file scope variables, which are declared by the keyword static. Those are not global variables, they are local private variables.

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

Should you use global variables? There are a few cases where it is fine:

In every other case, you shall not use global variables, ever. There is never a reason to do so. Instead use file scope variables, which is perfectly fine.

You should strive to write independent, autonomous code modules designed to do a specific task. Inside those modules, internal file scope variables should reside as private data members. This design method is known as object-orientation and widely acknowledged as good design.

Lundin
  • 17,577
  • 1
  • 24
  • 67
  • Why the downvote? – m.Alin Jan 29 '14 at 13:28
  • 2
    I *think* "global variable" can also be used to describe allocations to a global segment (not text, stack, or heap). In that sense file static and function static variables are/can be "global". In the context of this question, it is somewhat clear that global is referring to scope not allocation segment (though it is *possible* the OP did not know this). –  Jan 29 '14 at 14:14
  • 1
    @PaulA.Clayton I've never heard of a formal term called "global memory segment". Your variables will end up one of the following places: _registers_ or _stack_ (runtime allocation), _heap_ (runtime allocation), _.data_ segment (explicitly initialized static storage variables), _.bss_ segment (zeroed static storage variables), _.rodata_ (read-only constants) or _.text_ (part of the code). If they end up anywhere else, that's some project-specific setting. – Lundin Jan 29 '14 at 14:22
  • 1
    @PaulA.Clayton I suspect that what you refer to as "global segment" is the `.data` segment. – Lundin Jan 29 '14 at 14:24