Header guards are mechanism through which we can avoid inclusion of duplicate declarations/definitions if same header file is included multiple times directly or indirectly.
For instance let’s three files main.cpp, lib1.h and lib2.h. main.cpp includes both library header files. lib2.h also includes lib1.h. The default behavior when header guards are not defined, will include contents of lib1.h twice.
main.cpp
#include "lib1.h"
#include "lib2.h"
int main(int argc, char const *argv[])
{
return 0;
}lib1.h
int add(int a, int b);lib2.h
#include "lib1.h"
int sub(int a, int b);
When we run the following command to resolve all the #include directives,
g++-15 -v -ggdb -std=c++20 -E main.cpp -o include_directive
.i
include_directive.i file should have content as follows,
# 0 "main.cpp"
# 1 "/Users/nitinsharma/Learning/c++/header-guards//"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "main.cpp"
# 1 "lib1.h" 1
int add(int a, int b);
# 2 "main.cpp" 2
# 1 "lib2.h" 1
# 1 "lib1.h" 1
int add(int a, int b);
# 4 "lib2.h" 2
int sub(int a, int b);
# 3 "main.cpp" 2
int main(int argc, char const *argv[])
{
return 0;
}We can see there are two duplicate declarations for add(). One from lib1.h directly and second via lib2.h.
Add header guards
Header guards are created using conditional directives as such #ifndef, #define, and #endif.
lib1.h
#ifndef LIB1_H
#define LIB1_H
int add(int a, int b);
#endiflib2.h
#ifndef LIB2_H
#define LIB2_H
#include "lib1.h"
int sub(int a, int b);
#endifWith this in place if we run the same command to preprocesses include directives and open the result file again, we should get following content.
include_directive.i
# 0 "main.cpp"
# 1 "/Users/nitinsharma/Learning/c++/header-guards//"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "main.cpp"
# 1 "lib1.h" 1
int add(int a, int b);
# 2 "main.cpp" 2
# 1 "lib2.h" 1
int sub(int a, int b);
# 3 "main.cpp" 2
int main(int argc, char const *argv[])
{
return 0;
}
We can see that the declaration for add() is not duplicated.
Header guard limitation
When putting definitions in header files and using header guards, it can help in avoiding the one definition rule when header file is included multiple times. However, when there are different code files that use the header files, header guards do not work here. Header guards work only per code file.
For example, if there are two cpp files main.cpp and math.cpp. Both uses lib.h that contains a definition within the header guards.
lib.h
#ifndef LIB_H
#define LIB_H
int sub(int a, int b);
int add(int a, int b) {
return a + b;
}
#endifIncluding this file in both cpp files.
math.cpp
#include "lib.h"
int sub(int a, int b) {
return a - b;
}main.cpp
#include "lib.h"
int main() {
sub(1, 2);
add(1, 2);
return 0;
}When compiling this program with command,
/usr/local/Cellar/gcc/15.2.0/bin/g++-15 -v -ggdb -std=c++20 main.cpp math.cpp -o app
We should get duplicate linking error as shown below.
duplicate symbol 'add(int, int)' in:
/private/var/folders/_7/pv1rwdq15h14wkf5n7xqsv7w0000gn/T/ccYWbPFo.o
/private/var/folders/_7/pv1rwdq15h14wkf5n7xqsv7w0000gn/T/ccGlhNta.o
ld: 1 duplicate symbols
collect2: error: ld returned 1 exit status
So what actually happening here is that header guard will only work when header file is included in same cpp code file multiple times (directly or indirectly using another header file).
When there are multiple code files, they will have their header files opened up and when it comes to linking, there will be multiple definitions for same symbol.
So, both main.cpp and math.cpp will have add() definitions coming from header files.
#pragma once
#pragma directive is compiler specific and does not exists in C++ standards. It’s implementation varies across different compilers. However, #pragma once is an exception and should work mostly the same.
#pragma once can be used in place of header guards and it does the same thing. For example,
lib.h
#pragma once
int sub(int, int);
int add(int a, int b) {
return a + b;
}