Variables in header file - what's that all about?

In this post, we're going to look at how to safely declare and manage global variables in C when we are working with large number of source files.

So I was going through the code of one of my reportees in office. He is a fresh out of school and is my genius! He was struggling with a compilation error for days and that's when he came to me. I came to realize that he had written something like this in one of his header files:

/*--------------------------------------*/
/*!@brief structure to define something */
typedef struct
{
	uint32 variable_1;
	uint32 variable_2;
}variable_struct_s;
/*----------------------------*/
/*!@brief define the variable */
variable_struct_s variable_struct;

The compiler had been incessantly hounding him with this mysterious error claiming that the code had multiple instances of the variable "variable_struct." He browsed through his entire code, pulled a lot of hair, tried to talk nice to his computer, still found only one instance of this variable in this header file and no help! So what was he missing out?

He was missing out on the seldom talked about things called BASICS!. Not that he did anything wrong, it is something so obvious that almost nobody knows it until they first see it for themselves.

Let's do a rough sequencing of what happens when you include a header file in a source file:

#include "myheader.h"

This statement (with or without optimizations done by the compiler) copies the entire code in the header file myheader.h into the source file where the header is included. Hence when a header file containing a variable declaration as in the snippet above is included by multiple source files, the variable becomes a global variable for all those source files and you land up with a tonne of global variables with the same name - makes sense why the compiler was shouting at the poor fellow?

And no, it isn't actually the compiler, it is the linker!The compiler has a nature to keep good faith in the programmer. So, it will have (blind) faith that the programmer knows his stuff and will simply put the contents of the header into the source file -to every source file where the header file is written to be included.Then, it simply compiles each source file independently and generates the respective object files. The linker then comes in and picks up each file to link them all into a single library or binary. But then, it finds multiple declarations of the same variable in each of the object files and on the second sighting of the variable, complains that the variable is being redefined with information on the first use.

Circular Dependency
Image credits

So what is the solution to such a problem where you want to create a shared global variable?

The answer is pretty simple:

  1. Create a global variable in one of the most appropriate source file. Again make sure that the source file is not included by any of the other source files. You might complain, "Hah! Who does that!". Well, then make sure that you don't serve up the same file for linking twice (in your Makefile).
  2. Extern the global variable in the header file.

Here's one variant.

Source File Code:

#include <header_file.h>
/*----------------------------*/
/*!@brief define the variable */
variable_struct_s variable_struct;

Header File Code:

/*--------------------------------------*/
/*!@brief structure to define something */
typedef struct
{
	uint32 variable_1;
	uint32 variable_2;

}variable_struct_s;

/*----------------------------*/
/*!@brief extern the variable */
extern variable_struct_s variable_struct;

What we've done here is pretty simple, right?

Here's another variant.

Header File Code:

/*--------------------------------------*/
/*!@brief structure to define something */
typedef struct
{
	uint32 variable_1;
	uint32 variable_2;

}variable_struct_s;

#ifdef DEFINE_GLOBALS
/*!@brief An actual instance of the variable */
    variable_struct_s variable_struct;
#else
/*!@brief extern the variable */
extern variable_struct_s variable_struct;
#endif

Source file:

#define DEFINE_GLOBALS
#include <header_file.h>

Here, instead of actually declaring the global variable in a source file, we've put it in the header itself, but wrapped around it an armour of the preprocessor conditional ifdef. Then, in just one source file, we first declare that macro, and then include the header file. For every other source file, we get an extern. For that one source file, an actual instance gets created.

So, in this way you can avoid this circularity of declarations. Circularity reminds me of, what if you included the same header file twice. There ain't anyone stopping you from doing it, right? As a heads up, use a helmet.