GCC: Four Methods to Include Header File For Better Portability

Screenshot of console with gcc header

Imagine that you have a simple application, consisting of two files: a one platform-independent main.c source file:

#include "foo.h"

int main(void)
{
    platform_foo();
    return 0;
}

and a one platform-dependent foo.h header file:

#ifndef FOO_H_
#define FOO_H_

#include <stdio.h>

/* Assuming platform_foo() MUST be inline on every platform */
static inline void platform_foo(void)
{
    printf("Platform one foo!\n");
}

#endif

As long as you target only one platform you can complie everything directly:

$ gcc main.c
$ ./a.out
Platform one foo!

Now suppose that you’ve decided to port your app to another platform and you created a separate header with platform_foo() definition:

#ifndef FOO_H_
#define FOO_H_

#include <stdio.h>

/* Assuming platform_foo() MUST be inline on every platform */
static inline void platform_foo(void)
{
    printf("Platform two foo! I can do more!\n");
}

#endif

Now, how to control which header should be included?

Method 0: Using include directories

The most used approach. All you need to do is to arrange your project files as follows:

.
├── platform_one
│   └── foo.h
├── platform_two
│   └── foo.h
└── main.c

And then call GCC with an -I flag pointing to the right directory for desired platform:

$ gcc -I./platform_one main.c
$ ./a.out
Platform one foo!
$ gcc -I./platform_two main.c
$ ./a.out
Platform two foo! I can do more!

Method 1: Define a macro switch

A quite common approach as well. Use -D GCC flag to define a macro with a value that maps to the required header. The main.c file must be then modified to make use of that macro.

The project can be structured as:

.
├── platform_one_foo.h
├── platform_two_foo.h
└── main.c

Thus, the main.c file should look like:

#if USE_PLATFORM == 1
#include "platform_one_foo.h"
#elif USE_PLATFORM == 2
#include "platform_two_foo.h"
#endif

int main(void)
{
    print_foo();
    return 0;
}

And GCC must be called in following ways:

Platform one:

$ gcc -DUSE_PLATFORM=1 main.c
$ ./a.out
Platform one foo!

Platform two:

$ gcc -DUSE_PLATFORM=2 main.c
$ ./a.out
Platform two foo! I can do more!

Method 2: Define a header file name

According to the C standard (C99 6.10.2#4), following construct is totally legit:

#define USE_HEADER "foo.h"
#include USE_HEADER

So, we can define a header name via -D parameter and use it in main.c:

#include USE_HEADER

int main(void)
{
    print_foo();
    return 0;
}

The invocation of GCC is a little bit tricky. We need to pass both the header file name and surrounding quotes, too:

Platform one:

$ gcc -DUSE_HEADER="\"platform_one_foo.h\"" main.c
$ ./a.out
Platform one foo!

Platform two:

$ gcc -DUSE_HEADER="\"platform_two_foo.h\"" main.c
$ ./a.out
Platform two foo! I can do more!

Note that the outermost pair of quotes is required if your header file name contains spaces or characters that should be escaped.

Also, you can use < > instead of " " when defining a header name. E.g.:

-DUSE_HEADER="<foo.h>"

instead of

-DUSE_HEADER="\"foo.h\""

Method 3: Define a header file name without annoying escaping

Previous approach has introduced some mess with \" and <> in the GCC parameters.

What if we just want to pass a plain header name?

gcc -DUSE_HEADER=foo.h main.c

For this to work, we can use the stringizing trick . The stringizing is a way to convert a macro name to a string.

Let’s use that trick in main.c:

#define XSTR(s) STR(s)
#define STR(s) #s

#include STR(USE_HEADER)

int main(void)
{
    print_foo();
    return 0;
}

Now, calling the GCC will be a bit simpler:

Platform one:

$ gcc -DUSE_HEADER=platform_one_foo.h main.c
$ ./a.out
Platform one foo!

Platform two:

$ gcc -DUSE_HEADER=platform_two_foo.h main.c
$ ./a.out
Platform two foo! I can do more!

Method 4: GCC -include option

Pretty exotic option.

You need to specify a required header in -include option via the command line parameters. According to the GCC Preprocessor Options :

-include file

Process file as if #include “file” appeared as the first line of the primary source file. However, the first directory searched for file is the preprocessor’s working directory instead of the directory containing the main source file. If not found there, it is searched for in the remainder of the #include “…” search chain as normal.

If multiple -include options are given, the files are included in the order they appear on the command line.

https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html

main.c will become almost empty:

int main(void)
{
    print_foo();
    return 0;
}

Platform one:

$ gcc --include=foo.h main.c
$ ./a.out
Platform one foo!

Platform two:

$ gcc --include=extended_foo.h main.c
$ ./a.out
Platform two foo! I can do more!

Relevant references

Leave a Reply