Best Practice for Globals in C

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bear with me while I explain this. Let's say have three source files, lcd.c, menu.c and measurements.c.

Now I have a LCD related global that others source files need to know about.

What is the best practice for declaring the globals.

Option A
Declare the variable in lcd.c and an extern in every other file.

// lcd.c

uint8_t my_var;

/// ... Some LCD Code...

Where the other files are as follows

// menu.c

extern uint8_t my_var;

/// ... Some Menu Code...
// measurements.c

extern uint8_t my_var;

/// ... Some Measurment Code...

Option B
Declare the variable in lcd.c and the extern in a header file lcd.c

// lcd.c

#define CREATE_LCD_GLOBALS
#include "lcd.h"

uint8_t my_var;

/// ... Some LCD Code...

Where lcd.h is

// lcd.h

#ifndef CREATE_LCD_GLOBALS
 exterrn uint8_t my_var;
#endif

Where the other files are as follows

// menu.c

#include "lcd.h"

/// ... Some Menu Code...
// measurements.c

#include "lcd.h"

/// ... Some Measurement Code...

Option C
A third option is to declare both the variable and extern in a header file

// lcd.c

#define CREATE_LCD_GLOBALS
#include "lcd.h"

/// ... Some LCD Code...

Where lcd.h is

// lcd.h

#ifdef CREATE_LCD_GLOBALS
 uint8_t my_var;
#else exterrn uint8_t my_var;
#endif

Where the other files are as follows

// menu.c

#include "lcd.h"

/// ... Some Menu Code...
// measurements.c

#include "lcd.h"

/// ... Some Measurement Code...

My Question:

Which is the best practice or recommended practice?

Bear in mind I am documenting my code with Doxygen. Using option C I can't place the documentation for the variable directly above it's declaration because Doxygen's preprocessor won't pick up on it because of the #ifdef CREATE_LCD_GLOBALS.
So using option C (which is how I currently do it) lcd.h becomes

// lcd.h

/*!
* @var   my_var
* @brief Description of my_var
*
* @details N.B. The documentation is in a different place from the declaration */

#ifdef CREATE_LCD_GLOBALS
 uint8_t my_var;
#else exterrn uint8_t my_var;
#endif

????

Thanks.

Ben
-Using IAR (& ocasionally CodeVision)
0.7734
1101111011000000110111101101

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

B every time (forum wouldn't let me post just "B" :-()

The C++ mob will tel you to keep it "private" in lcd.c and provide accessor functions ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
B every time (forum wouldn't let me post just "B" :-()

Thanks.

Quote:
The C++ mob will tel you to keep it "private" in lcd.c and provide accessor functions ;-)

Oh to be working on C++ !!! :-)

Ben
-Using IAR (& ocasionally CodeVision)
0.7734
1101111011000000110111101101

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tend to prefer Option C from your choices myself. Although, to save on repetition of variable names, I tend to include:

#define MAIN

in the file where main() resides, and then include:

#ifdef MAIN
#define EXT
#else
#define EXT extern
#endif

in the header file. In this way, many variables can be defined in the header file as both local and external variables with the one declaration. For example:

EXT uint8_t my_var;

From here,

#include "lcd.h"

can be used in each file that requires the variables.

I prefer this, although I don't suppose it would be deeemed best practice.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I tend to include:
Code:
#define MAIN

in the file where main() resides, and then include: ...


Sounds interesting, but doesn't it break down when more levels than MAIN and one lower are used?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch - I'm afraid I don't think I understand what you mean by:

Quote:
...more levels than MAIN and one lower...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
Master file
   main()

Subsystem A
   include low-level1
   include low-level2

Subsystem B
   include low-level 3
...

Low-leveln also has globals, and externs needed by the Subsystem but which are never seen by main().

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

NB: I also struggled with this years ago in my first bigger projects with "structure". (Back then, coding was done with parchment scribed under candle light, so we were all C learners.)

Whether "best practice" or wherever we picked it up, the include file had externs for all the data. Even the C "driver" file itself picked up global info from the include file. The definitions were in a separate stub file with typically only definitions and not code.

Plotter driver
plotdrv.c
    include plotdrv.h

plotdot.c
    include plotdrv.h

plotline.c
    include plotdrv.h
...
plotdata.c
    include plotdrv.h

==============================
plotdrv.h
    extern int plotx;
    extern int ploty;
    function prototypes

plotdata.c
    include plotdrv.h  (processing the externs ensures a match
    int plotx;
    int ploty;

I'm not saying it is "best". Just something we used. Yes, the definitions need to be cut/pasted to the .h and the extern added.

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Thu. May 20, 2010 - 04:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
B every time (forum wouldn't let me post just "B" :-()

Yep. Also makes it easy to convert the .c to the .h also (you could even write a utility)

Quote:
The C++ mob will tel you to keep it "private" in lcd.c and provide accessor functions ;-)

Which can often be inline.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think it 'breaks down' as such, but I take the point. That was just an example of what I do.

When I have situations such as the others you describe, each futher set of files that have common globals would have another header with a #define to denote the 'main' file for the functional software, such as

#define LCD_MAIN

in lcd.c, for example, and a similar set of variable definitions as previously described. Further files could then include this extra header file.

Of course, all globals for the entire program could be included in the one header, but this would seem a bit unnecessary.

I am beginning to suspect that this definitely not best practice, but it has worked well for me so far.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would declare it in lcd.h, and include lcd.h anywhere the variable will be referenced.

// lcd.h
extern uint8_t my_var;

and define it once in main.c

#include "lcd.h"
uint8_t my_var;

It is OK to have both the declaration and definition in the same file.
/mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lee,

Your last quoted filename is surely plotdata.c not plotdata.h ? That is in:

plotdata.h
    include plotdrv.h  (processing the externs ensures a match
    int plotx;
    int ploty;

Cliff

PS unlike n1st I tend to have a separate global.c with just var definitions rather than putting them in main.c - YMMV

PPS on "big" projects every module "owns" its own globals and their definition is usually in module_name.c with delcarations in "module_name.h"

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A is junk.
B is junk.
C is junk.

Instead:

Declare globals in .h file(s).
Define globals in .c file(s).

And forget about preprocessor trickery like #define CREATE_LCD_GLOBALS.

There is nothing more to say about this.

Stealing Proteus doesn't make you an engineer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Your last quoted filename is surely plotdata.c not plotdata.h ?

Yes. Edited.
Quote:

And forget about preprocessor trickery like #define CREATE_LCD_GLOBALS.


I tend to agree, for normal apps. "Never say never"--if the number is overwhelming (e.g., a computer-generated file that gets regenerated and a separate structure/array for each font entry (say), then I can see it.

Quote:

PPS on "big" projects every module "owns" its own globals and their definition is usually in module_name.c with delcarations in "module_name.h"

In the partial example that I gave, your method broke down. The "plotter driver" was too big for one file to contain it. ;) (Rather, the primitives were broken out from the "driver". An easy example is the primitives could be "overloaded" at build time to leave the API the same but be tailored for different devices.)

Some primitives need to "share" some info. They don't "own" all pieces of global data. Thus arose the stub file with just the definitions.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

davidclark2000 wrote:
I tend to prefer Option C from your choices myself. Although, to save on repetition of variable names, I tend to include:

#define MAIN

in the file where main() resides, and then include:

#ifdef MAIN
#define EXT
#else
#define EXT extern
#endif

in the header file. In this way, many variables can be defined in the header file as both local and external variables with the one declaration. For example:

EXT uint8_t my_var;

From here,

#include "lcd.h"

can be used in each file that requires the variables.

I prefer this, although I don't suppose it would be deeemed best practice.

I do this also.
It works for me.
Although, to be honest, for AVR stuff I tend not to split things up into lots of separate files. I never saw the point of chasing round trying to remember where some function
lives.

Four legs good, two legs bad, three legs stable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ArnoldB wrote:
A is junk.
B is junk.
C is junk.

Instead:

Declare globals in .h file(s).
Define globals in .c file(s).


Sorry in my answer above I thought B was this - I agree all A, B, C are junk and the way to do it is exactly as Arnold describes here.

While this sounds like an "old record" I'd say that if you want to see a good example of large project C then the Linux kernel is open for everyone to read.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

what arnold and clawson said.

lcd.c

uint8_t a;
static uint8_t b;

lcd.h

extern uint8_t a;
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm with Arnold and clawson, despite me hanging out with the C++ mob occasionally.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]