This is a cool trick I just learned from commenter Sean Patrick Conner in a previous post.
If you want to have variables globally available, but want to have some control over how they are set, you can limit the variables to be static to a file containing “get” and “set” functions:
static int S_Number = 0;
void SetNumber (int number)
{
S_Number = number;
}
int GetNumber (void)
{
return S_Number;
}
This allows you to add range checking or other things that might make sense:
void SetPowerLevel (int powerLevel)
{
if ((powerLevel >= 0) || (powerLevel <= 100))
{
S_PowerLevel = powerLevel;
}
}
Using functions to get and set variables adds extra code, and also slows down access to those variables since it is having to jump in to a function each time you want to change the variable.
The benefit of adding range checking may be worth the extra code/speed, but just reading a variable has not reason to need that overhead.
Thus, Sean’s tip…
Variables declared globally in a file cannot be accessed anywhere else unless you use “extern” to declare them in any file that wants to use them. You might declare some globals in globals.c like this:
// Globals.c
int g_number;
…but trying to access “g_number” anywhere else will not work. You either need to add:
extern int g_number;
…in any file that wants access to it, or, better, make something like globals.h that contains all your extern references:
// Globals.h
extern int g_number;
Now any file that needs access to the globals can just include “globals.h” and use them:
#include "globals.h"
void function (void)
{
printf ("Number: %d\n", g_number);
}
That was not Sean’s tip.
Sean mentioned something that makes sense, but I do not think I’d ever tried: The extern can contain the “const” keyword, even if the declaration of the variable does not!
This means you could have a global variable like above, but in globals.h do this:
// Globals.h
extern int const g_number;
Now any file that includes “globals.h” has access to g_number as a read-only variable. The compiler will not let code build if there is a line trying to modify it other than globals.c where it was actually declared non-const.
Thus, you could access this variable as fast as any global, but not modify it. For that, you’d need a set routine:
// Globals.c
int c_number; // c_ to indicate it is const, which it really isn't.
// Set functions
void SetNumber (int number)
{
c_number = number;
}
Now other code can include “globals.h” and have read-only access to the variable directly, but can only set it by going through the set function, which could enforce data validation or other rules — something just setting it directly could not.
#include "Globals.h"
int main(int argc, char **argv)
{
printf ("Number: %d\n", c_number);
SetNumber (42);
printf ("Number: %d\n", c_number);
return 0;
}
That seems quite obvious now that I have been shown it. But I’ve never tried it. I have made plenty of Get/Set routines over the years (often to deal with making variable access thread-safe), but I guess it never dawns on me that, when not dealing with thread-safe variables, I could have direct read-only access to a variable, but still modify it through a function.
Global or static?
One interesting benefit is that any other code that needed direct access to this variable (for speed reasons or whatever) could just add its own extern rather than using the include “Globals.h”:
// Do this myself so I can modify it
extern int c_number;
void MyCode (void)
{
// It's my variable and I can do what I want with it!
c_number = 100;
}
By using the global, it opens up that as a possibility.
And since functions are used to set them, they could also exist to initialize them.
// Globals.c
// Declared as non-const, but named with "c_" to indicate the rest of the
// code cannot modify it.
int c_number;
// Init functions
void InitGlobals (void)
{
c_number = 42;
}
// Set functions.
void SetNumber (int number)
{
c_number = number;
}
// Globals.h
// Extern as a const so it is a read-only.
extern int const c_number;
// Prototypes
void InitGlobals (void);
void SetNumber (int number);
#include <stdio.h>
#include "Globals.h"
int main()
{
InitGlobals ();
printf ("c_number = %d\n", c_number);
// This won't work.
//c_number = 100;
SetNumber (100);
printf ("c_number = %d\n", c_number);
return 0;
}
Spiffy.
I had thought about using static to prevent the “extern” trick from working, but realize if you did that, there would be no read-only access outside of that file and a get function would be needed. And we already knew how to do that.
I love learning new techniques like this. The code I maintain in my day job has TONS of globals for various reasons, and often has duplicate code to do range checking and such. I could see using something like this to clean all of that up and still retain speed when accessing the variables.
Got any C tricks? Comment away…
One thing I’ve recently started doing is the following to double-check my assumptions about the environment at compile time:
enum
{
BYTE_ADDRESSABILITY = 1 / (CHAR_BIT == 8),
CHAR_RANGE_LOW = 1 / (SCHAR_MIN == -128),
CHAR_RANGE_HIGH = 1 / (SCHAR_MAX == 127),
INT_SIZE = 1 / (sizeof(int) == 4),
};
instead of calling
assert()
at runtime. Another thing I do is avoid macros, especially those that look like a function. Instead of:#define max(a,b) ((a) > (b) ? (a) : (b))
I’ll write an inline function as (if I can use C99):
static inline size_t max(size_t a,size_t b)
{
return a > b ? a : b;
}
That way, I avoid potentially unwanted side effects when evaluating an expression twice, and I get type safety as well. Yes,
max()
as a function is limited to (as written)size_t
values, but generally, most functions-replacing-macros aren’t generic (in my cases) anyway.Yet another thing I do, is if a function takes a pointer to be used as an array, I’ll declare the function as:
extern int foo(struct foo foos[],size_t n);
instead of
extern int foo(struct foo *foos,size_t n);
The end result is the same,
foos
is a pointer to astruct foo
, but the intent thatfoos
is an array is more visible in the former than the later. The only exception to this are strings, which I still mark aschar *
because it’s so idiomatic. Further showing intent, I’ll mark pointer parameters asconst
:extern int bar(struct bar const *pbar,...);
To inform a later programmer that the passed in structure isn’t changed by this function. I find it easier to start with
const
parameters and only later change to removeconst
(if I find it overly restrictive) that it is to start withoutconst
only to have to add it later (const
is very viral on a codebase—addingconst
to a function parameter is often an invasive act after the fact).One of these days I’ll need to write a post about how I write C code.
I would very much like to see such a post.
I need to test and see if inline is even supported on the embedded compilers I use. I know at a previous job, I had to change a function to a macro to save a few bytes each time, and I “thought” I’d tried inline and figured it wasn’t working. With my current compiler, I can easily look at the assembly it generates so I may have to do a quick test.