The #include directive in C tells the preprocessor to look for a header file (.h) and copy its contents into the source file (.c)
The way the preprocessor searches for the file depends on the delimiters you use: double quotes (“”) or angle brackets (<>)
#include "file.h"
- Header files for your own project
- search: 1. Current directory (where the .c file is located). 2. Specified paths (using GCC’s -I flag). 3. Standard system paths (optional, compiler dependent).
- Source File Directory: The search starts in the same folder where the .c file containing the #include is located
- Custom Paths (-I): If not found there, look in any path you specified when compiling using the -I flag (e.g., -I headers).
#include <stdio.h>
- Header files of the Standard C Library or system libraries
- search: 1. Standard System Paths (directories predefined by the compiler, such as /usr/include)
- System Paths: The search is limited to the paths already configured internally by the compiler (where libc, math.h, etc. are located). This ensures that the correct version of the standard library file is always used, without confusion with similarly named files in your project.

Implementations of C Standard Library functions (such as printf, scanf, fopen, etc.) are not located in a single source file called stdio.c that you can access.
This binary file contains the machine code for all functions declared in headers such as stdio.h, stdlib.h, string.h, etc.
The binary for the stdio.h functions (the equivalent of a giant stdio.o) is built into the operating system and used by the linker (ld) which is also seen in your /bin folder.
The #include directive does two main things in the C preprocessing process:
- It is a “copy and paste” of the text from the .h file to the .c file
- defines the functions themselves in a .h
It is absolutely necessary to define the symbol XXXX_H within the .h file
The symbol XXXX_H (or whatever unique name you choose) is the key to the include guard.
If that symbol weren’t defined in the #define line, the protection mechanism simply wouldn’t work.
define is the key
The purpose of the directives is the prevention of double inclusion, and the process is sequential.
Check (#ifndef): The preprocessor checks: Does the symbol XXXX_H exist?
If it does NOT exist (First Time): The condition is true. The preprocessor then executes the following line: #define XXXX_H. This line creates the symbol in the preprocessor’s symbol table. The rest of the code is then processed.
If YES exists (Second Time): The #ifndef condition is false because the symbol was already defined in the previous step. The preprocessor skips all code up to #endif
The #define is the switch that changes the state of the system for future inclusions
The symbol you use as an inclusion guard (XXXX_H) does not have a mandatory nomenclature imposed by the C language or the preprocessor
The industry standard convention is to create a symbol that is globally unique within your project and related to the file name
- Use Capital Letters: The entire name must be in capital letters
- Use Underscores (_): Use underscores instead of periods, slashes, or hyphens
- Based on File Name: Replace periods (.) with underscores and add H or _H at the end
The file name convention (FILE_NAME H) minimizes the possibility of collision with any other symbol in any other file
In software development, there is a strong convention (almost mandatory due to good practices) that you must follow to ensure the uniqueness of the symbol
The only strict requirement for that symbol is that it must be unique
If two different .h files use the same guard, the second file will not be included, causing “undefined symbol” errors in your program
#ifndef XXXX_H
#define XXXX_H
// ...
#endif
xxxx is the filename of .h
It is crucial to know how to use #include correctly, not for the speed of the resulting program, but for the speed of compilation and the integrity of the code
The high efficiency of C is due to:
Absence of Garbage Collector: There is no background process (as in Java) that pauses the program to manage memory.
The programmer (you, using malloc() and free()) has complete control over when memory is allocated and freed
Simple Types: The data representation is very simple and maps directly to the hardware (e.g., an int is the native word size of the CPU)
Low Level: C is designed to be a “portable abstraction of assembly.” It is very close to the hardware, allowing the compiler to generate highly optimized machine code that interacts directly with memory and the CPU
The way you use #include affects compilation speed and code correctness, not the speed of the final executable
Guardians of Inclusion (#ifndef):
They prevent the preprocessor from reading and pasting the same header code repeatedly, which speeds up the compilation process and prevents fatal redefinition errors
The efficiency of the program (at runtime) comes from the design of the C language