GCC: Four Methods to Include Header File For Better Portability

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!