1. Introduction

In the world of C and C++ programming, we cannot stress enough the importance of the include directories. When we write a program, it often depends on various header files – some are user-defined, while others belong to standard libraries like <stdio.h>, <iostream>, and more. These files house the definitions and declarations that our programs rely upon.

When the compiler translates our source code into machine code, it must know where to find these header files. That’s where the include directories step in. They act as repositories or directories telling the compiler, “Look here! This is where you’ll find the information you need.”

In this tutorial, we’ll uncover how GNU Compiler Collection (GCC) determines its default include directories and discuss the methods to discover them. Let’s get started!

2. Hardcoded Paths vs. Dynamic Retrieval

The ability of GCC to automatically detect include directories isn’t just a result of hardcoding paths into the compiler’s codebase. While there are indeed paths that GCC is programmed to know from the get-go, there’s more to the story.

When we install GCC on our operating system (OS), the compiler learns about the system’s structure, adapting its knowledge to the storage of standard libraries and headers. This dynamic nature ensures that GCC remains versatile across various systems and distributions.

Furthermore, the debate between hardcoded paths and dynamic retrieval isn’t about one being superior to the other. Instead, it’s about understanding how these two approaches complement each other.

At its core, GCC comes with a set of predetermined paths: the Hardwired Paths. These are the locations GCC will always check, ensuring consistency. It’s like having a default checklist that GCC refers to every time.

On the other hand, on installation or during specific updates, GCC interacts with the OS, assimilating knowledge about the system’s layout. This mechanism ensures GCC remains adaptable, catering to unique system structures or when standard libraries are housed in non-default locations. This we refer to as the Dynamic Retrieval from the OS.

3. Exploring Default Paths for C and C++

While understanding the theory behind GCC’s behavior is enlightening, practical insights often demand a hands-on approach. Let’s enter the command line – GCC’s playground and a treasure trove of information. By running specific commands, we can prompt GCC to reveal its list of default include directories. This approach isn’t just about discovery; it’s also a testament to GCC’s transparent nature, always ready to communicate with those curious enough to ask.

3.1. Checking the Default Paths for C

With the gcc command, we can coax GCC into revealing its default include paths for C:

$ gcc -xc -E -v -
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
Target: x86_64-linux-gnu
Configured with: ... [long line with configuration options]
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

Let’s break down our command input:

  • gcc – invokes the compiler to compile C, C++, Fortran, and other languages
  • -xc – passes code through standard input and compiles the input as C code, regardless of the file extension
  • -E – stops the compilation process after the preprocessing phase, responsible for handling preprocessor directives like #include and #define
  • -v – makes gcc print verbose output, showing the exact sequence of commands it invokes and paths it searches

Furthermore, from our output, we can see a wealth of information. The first few lines provide information about the GCC installation, its version, and the system’s architecture:

  • COLLECT_GCC_OPTIONS – indicates which options are being used during this compilation phase
  • /usr/lib/gcc/x86_64-linux-gnu/7/cc1cc1 is the internal C compiler that gcc invokes to indicate the actual command used for the preprocessing
  • ignoring nonexistent directory – indicate directories that GCC might have been configured to search for header files but don’t exist on the system
  • #include “…” search starts here – show the exact directories where GCC will look for header files while considering the listed order

Notably, this information is handy for dealing with compilation errors related to GCC not finding header files or understanding how GCC has been configured on a particular system.

3.2. Checking the Default Paths for C++

On the other hand, let’s explore the default paths of C++:

$ g++ -xc++ -E -v -
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
Target: x86_64-linux-gnu
Configured with: ... [long line with configuration details]
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
COLLECT_GCC_OPTIONS='-E' '-v' '-xc++' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/9/cc1plus -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/9
 /usr/include/x86_64-linux-gnu/c++/9
 /usr/include/c++/9/backward
 /usr/lib/gcc/x86_64-linux-gnu/9/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/9/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

Similar to our previous interaction and output with C:

  • g++ – invokes the C++ front-end of the GCC
  • -xc++ – tells g++ to treat the input as C++ code, regardless of file extension

As with the gcc command, this information can be invaluable for debugging compilation issues or understanding the compiler setup on a specific system.

We should note that some of the listed paths are specific to C++ (such as /usr/include/c++/9) and were therefore absent from the gcc output shown previously.

4. Shorter Command for Streamlined Output

While the verbose output of GCC offers a detailed perspective, it can be overwhelming for us if our primary interest is the include paths. Let’s enter the magic of UNIX utilities.

If we desire a more concise output that focuses exclusively on the include directories, we can utilize a gcc one-liner:

$ gcc -E -Wp,-v -xc /dev/null 2>&1 | sed -n '/#include </,/> search ends here:/p'
#include <...> search starts here:
 /usr/include/c++/9
 /usr/include/x86_64-linux-gnu/c++/9
 /usr/include/c++/9/backward
 /usr/lib/gcc/x86_64-linux-gnu/9/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/9/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

In this one-liner:

  • -Wp,-v – passes the verbose (-v) flag directly to the preprocessor
  • -xc – treats the input as a C code
  • /dev/null – acts as a dummy input file that’s always empty since we’re only interested in the paths and not preprocessing any code
  • 2>&1 – redirects the standard error (where the verbose output goes) to standard output so we can process it with sed
  • sed -n ‘/#include </,/> search ends here:/p’ – uses sed to filter the output and only print lines between the two patterns (#include <…> and > search ends here:)

Notably, we can also replace gcc with g++ in this one-liner to obtain the output for C++.

5. Multi-Architectural GCC Versions

Every tool and framework comes with exceptions and exceptional cases. In multi-arch systems, where multiple architectures coexist (for example, both 32-bit and 64-bit), GCC’s behavior might deviate slightly. The default include directories might differ based on the architecture we’re targeting.

For instance, we can use the m32 flag to see a system with both 32-bit and 64-bit architectures (a multi-arch system). This tells gcc to generate code for a 32-bit environment and often changes the search directories since 32-bit and 64-bit environments usually have different standard libraries and headers:

$ gcc -m32 -E -Wp,-v -xc /dev/null 2>&1
#include <...> search starts here:
 /usr/include/c++/9
 /usr/include/i386-linux-gnu/c++/9
 /usr/include/c++/9/backward
 /usr/lib/gcc/i386-linux-gnu/9/include
 /usr/local/include
 /usr/lib/gcc/i386-linux-gnu/9/include-fixed
 /usr/include/i386-linux-gnu
 /usr/include
End of search list.

There are a few key differences from the 64-bit version we’ve previously seen. First, the architecture-specific directory will often have a different name, such as i386-linux-gnu instead of x86_64-linux-gnu. Also, the paths for the standard libraries and headers will point to the 32-bit versions.

However, we must remember that the default paths could change based on other command-line parameters or macros. For instance, if we’re using specific build systems or additional configuration flags, the resultant include directories might differ. Therefore, we should know the exact parameters and flags we’re using when fetching GCC’s default include directories.

6. Using a Compiler-Specific Approach

GCC is a collection of compilers for languages like C, C++, Fortran, and others. As such, it uses a particular program for each language’s compilation. For C, it’s cc1. Knowing this, we can take a unique approach to extract include paths.

Instead of asking GCC directly, we can request the cc1 program (used for C) to divulge its secrets. The -print-prog-name option is an internal diagnostic utility provided by gcc to print the full path to one of its internal tools. When we pass cc1 as the argument to this option, it returns the full path to the cc1 program that gcc would use to compile C code:

$ gcc -print-prog-name=cc1 -v
/path/to/cc1 version 9.3.0
ignoring nonexistent directory "/usr/local/include/i386-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/i386-linux-gnu/9/../../../../i386-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/9
 /usr/include/i386-linux-gnu/c++/9
 /usr/include/c++/9/backward
 /usr/lib/gcc/i386-linux-gnu/9/include
 /usr/local/include
 /usr/lib/gcc/i386-linux-gnu/9/include-fixed
 /usr/include/i386-linux-gnu
 /usr/include
End of search list.

Similar to our previous interaction, the beginning of the output provides the full path to cc1 and its version. After that, it will display directories it’s set to search for headers and any directories it’s set to ignore because they don’t exist. This method provides a more direct approach to finding out the include paths since it queries the exact program (cc1) that gcc uses for C compilation. The paths listed are the ones cc1 will search when looking for header files.

7. Common Pitfalls and Troubleshooting

Even as we understand the structure of GCC’s default include directories, it’s not uncommon to stumble upon inevitable roadblocks. Let’s briefly discuss a few.

7.1. Directory Not Found Errors

Sometimes, a listed directory might not exist. This can be due to various reasons, including but not limited to OS updates or package misconfigurations. To fix this, we should double-check our paths.

7.2. Conflicting Versions

In systems where multiple GCC versions coexist, there might be ambiguity about which one we’re currently using. We should always call the desired version, as discussed earlier in the Multi-Architectural GCC Versions section.

7.3. Custom Installation Paths

If GCC was not installed through standard package managers, its include directories might not be where we expected. It’s necessary always to specify paths when doing non-standard installations.

Ultimately, understanding these pitfalls not only streamlines the coding process but can also save countless hours of debugging. By grasping the defaults while being aware of potential hiccups, we ensure a smoother development experience.

8. Conclusion

In this article, we delved into the realm of GCC and its default include directories. From understanding the fundamental mechanisms that allow GCC to automatically find standard include files to exploring specific command-line techniques for revealing these paths, we have covered a comprehensive range of topics.

Finally, we navigated the intricacies of command flags, showcased practical code samples, and even touched upon common pitfalls and how to troubleshoot them.

Comments are closed on this article!