C Programming | Working with headers

FreeBSD gearing-up

Index


Eventually at a certain point in our development, we'll have a big single source file or we'll need to reuse code from a source file in another source. Header files are just source C files with a .h extension that have C code inside. They are designed to store function declarations and macros.

Header files are not mandatory but play a big game when sharing code between source files. They also help creating documentation and make code cleaner and more tidy.

Why we need header files

The way C is designed, requires the programmer to declare what functions he's going to use before defining them. This means that the compiler needs to know that there is some function Foo that takes parameters x and y before taking care of what Foo does inside.

There are two types of C header files:

The first ones are provided by the C standard library, the GNU C library and similar.

The user-defined header files are the ones that we need to create manually and fill with our content.

— If we create a function calcradius that takes one float value for a circle's circumference and return the result, we have to declare it first:

//function declaration
float calcradius (float circumference);

//function definition
float calcradius (float circumference) {
    float result;
    result = circumference / 2 * 3.14f;
    return result;
}

The function at the beginning is a declaration. It exists somewhere in the program but there's no memory allocated to it.

The detailed function below is the function definition.

— Header files describe what you can use from the outside module while the function definitions are stored in a source file with a .c extension.

When the compiler runs, it copies and pastes each header file included in a source file at the beginning of the code.

To implement a header in our calculation program we need to create two files, math.h and math.c.

Now we can cut and paste our function declaration inside math.h:

math.h

//function declaration
float calcradius (float circumference);

In order to link them together, inside the math.c file we need to add the math.h file at the beginning of the program, using the #include directive:

math.c

#include "math.h"

//function definition
float calcradius (float circumference) {
    float result;
    result = circumference / 2 * 3.14f;
    return result;
}

This should be enough for the .c source file however, inside our .h file we have to perform some extra work in order to prevent some future errors that can happen when our program grows.

Header guards

We know when the compiler runs, it's going to copy-paste our header file in each #include directive. Since we can include the same header file in multiple .c source files we need a way to not copy-paste the same header multiple times.

The C programming language provides the #ifndef and #define directives to help with this problem.

#ifdef's are pre-processor directives that are here needed to ensure the header file is only included once. Otherwise we would face an error similar to this when we run the compiler:

./math.h:4:7 error redefinition of calcradius function
./math.h:4:7 note previous definition is here

They work similar to if statements. The best way to protect our header for duplication is to name it with a unique identifier (usually its file name) and check if it's already defined or not. If not, then it's copied until the end of the condition.

Let's guard our header file:

math.h

#ifndef MATH_H /* This is our identifier */
#define MATH_H 

//function declaration
float calcradius (float circumference);

#endif /* MATH_H */

This way we can ensure that no matter how many times we need to include math.h in our program sources.

Compiler guards

Sometimes the C source code is compiled with a C++ compiler. This can be because of part of the program has been written in C++, or we have to use some modules that are created in C++. It also can be the case where we are including some C modules inside a C++ project.

— While C don't, C++ does name mangling due to function overloading. In C++ we can have two functions that have the same name with different arguments or return values, and the program can run without problems.

extern "C" {} ensures the compiler to treat the code inside the extern as C code.

math.h

#ifndef MATH_H 
#define MATH_H 

#ifdef __cplusplus
extern "C" {
#endif

//function declaration
float calcradius (float circumference);

#ifdef __cplusplus
}
#endif

#endif /* MATH_H */

Now we can work with the function calcradius in any other .c source file without worrying when our program grows.

Other types to include

We've seen how to declare functions in header files and define them later in source files. In header files we can also include structs and variables without giving them any values. In the .c source file we can access those variables and initialize them.

If we initialize variables with values in header files, the compiler is going to prompt an error when it runs.

math.h

...
float givenCircumference;
...

— There's an exception with declaring values in header files, which are constants. We know (in the example above) that 3.14~ is the PI value and that value never changes, it's a constant.

Let's add it into our header by defining the value:

math.h

#define PI 3.14159f
...
float givenCircumference;
...
math.c

...
result = circumference / 2 * PI;
...

Our final files should look like this:

math.h

#ifndef MATH_H /* This is our identifier */
#define MATH_H 

#define PI 3.14159f
float radius;
float circumference;


#ifdef __cplusplus
extern "C" {
#endif

//function declaration
float calcradius (float circumference);

#ifdef __cplusplus
}
#endif

#endif /* MATH_H */

math.c

#include "math.h"

//function definition
float calcradius (float circumference) {
    float result;
    result = circumference / 2 * PI;
    return result;
}

Summing up

We can create a main.c file to perform the needed operations, and by including the math.h header, we should be able to access any function or value stored inside it, since the compiler is going to know where to find those values and functions when it runs.

main.c

#include <stdio.h>
#include "math.h"

int main() {
    /* assign a value for our declared float */
    given_circumference = 4.8f;

    /* execute our function */
    printf("Radius from circle is: %.2f \n", calcradius(givenCircumference));

    return 0;
    }

Now you can start growing up your own math library and use it in every project that needs one (: