Asked  7 Months ago    Answers:  5   Viewed   39 times

I've read that there is some compiler optimization when using #pragma once which can result in faster compilation. I recognize that is non-standard, and thus could pose a cross-platform compatibility issue.

Is this something that is supported by most modern compilers on non-windows platforms (gcc)?

I want to avoid platform compilation issues, but also want to avoid the extra work of fallback guards:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Should I be concerned? Should I expend any further mental energy on this?

 Answers

49

Using #pragma once should work on any modern compiler, but I don't see any reason not to use a standard #ifndef include guard. It works just fine. The one caveat is that GCC didn't support #pragma once before version 3.4.

I also found that, at least on GCC, it recognizes the standard #ifndef include guard and optimizes it, so it shouldn't be much slower than #pragma once.

Tuesday, June 1, 2021
 
ALH
answered 7 Months ago
ALH
67

The preprocessor is a program that takes your program, makes some changes (for example include files (#include), macro expansion (#define), and basically everything that starts with #) and gives the "clean" result to the compiler.

The preprocessor works like this when it sees #include:

When you write:

#include "some_file"

The contents of some_file almost literally get copy pasted into the file including it. Now if you have:

a.h:
class A { int a; };

And:

b.h:
#include "a.h"
class B { int b; };

And:

main.cpp:
#include "a.h"
#include "b.h"

You get:

main.cpp:
class A { int a; };  // From #include "a.h"
class A { int a; };  // From #include "b.h"
class B { int b; };  // From #include "b.h"

Now you can see how A is redefined.

When you write guards, they become like this:

a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif

b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif

So now let's look at how #includes in main would be expanded (this is exactly, like the previous case: copy-paste)

main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H          // From
#define A_H          // #include "a.h"
class A { int a; };  // inside
#endif               // "b.h"
class B { int b; };
#endif

Now let's follow the preprocessor and see what "real" code comes out of this. I will go line by line:

// From #include "a.h"

Comment. Ignore! Continue:

#ifndef A_H

Is A_H defined? No! Then continue:

#define A_H

Ok now A_H is defined. Continue:

class A { int a; };

This is not something for preprocessor, so just leave it be. Continue:

#endif

The previous if finished here. Continue:

// From #include "b.h"

Comment. Ignore! Continue:

#ifndef B_H

Is B_H defined? No! Then continue:

#define B_H

Ok now B_H is defined. Continue:

#ifndef A_H          // From

Is A_H defined? YES! Then ignore until corresponding #endif:

#define A_H          // #include "a.h"

Ignore

class A { int a; };  // inside

Ignore

#endif               // "b.h"

The previous if finished here. Continue:

class B { int b; };

This is not something for preprocessor, so just leave it be. Continue:

#endif

The previous if finished here.

That is, after the preprocessor is done with the file, this is what the compiler sees:

main.cpp
class A { int a; };
class B { int b; };

So as you can see, anything that can get #included in the same file twice, whether directly or indirectly needs to be guarded. Since .h files are always very likely to be included twice, it is good if you guard ALL your .h files.

P.S. Note that you also have circular #includes. Imagine the preprocessor copy-pasting the code of Physics.h into GameObject.h which sees there is an #include "GameObject.h" which means copy GameObject.h into itself. When you copy, you again get #include "Pysics.h" and you are stuck in a loop forever. Compilers prevent that, but that means your #includes are half-done.

Before saying how to fix this, you should know another thing.

If you have:

#include "b.h"

class A
{
    B b;
};

Then the compiler needs to know everything about b, most importantly, what variables it has etc so that it would know how many bytes it should put in place of b in A.

However, if you have:

class A
{
    B *b;
};

Then the compiler doesn't really need to know anything about B (since pointers, regardless of the type have the same size). The only thing it needs to know about B is that it exists!

So you do something called "forward declaration":

class B;  // This line just says B exists

class A
{
    B *b;
};

This is very similar to many other things you do in header files such as:

int function(int x);  // This is forward declaration

class A
{
public:
    void do_something(); // This is forward declaration
}
Tuesday, June 1, 2021
 
Troncoso
answered 7 Months ago
30
Does underscore convention matter?

Yes. It matters.

Identifiers with a leading underscore followed upper case letter is reserved for implementation. So what you have would cause undefined behaviour.

The following is the C standard's specification for naming the identifiers (C11 draft):

7.1.3 Reserved identifiers

Each header declares or defines all identifiers listed in its associated subclause, and optionally declares or defines identifiers listed in its associated future library directions subclause and identifiers which are always reserved either for any use or for use as file scope identifiers.

— All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.

— All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

— Each macro name in any of the following subclauses (including the future library directions) is reserved for use as specified if any of its associated headers is included; unless explicitly stated otherwise (see 7.1.4). — All identifiers with external linkage in any of the following subclauses (including the future library directions) and errno are always reserved for use as identifiers with external linkage.184) — Each identifier with file scope listed in any of the following subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included.

No other identifiers are reserved. If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.

If the program removes (with #undef) any macro definition of an identifier in the first group listed above, the behavior is undefined.

Without violating any of the above, the include guard name can be anything and doesn't have to be the name of the header file. But usually the convention I have seen/used is to use same name as that of the header file name so that it doesn't cause any unnecessary confusion.

Friday, July 30, 2021
 
krs8785
answered 4 Months ago
66

If you open the file to read the contents (you can even right click the include directive in most editors to open the file), you will see that include files usually start with something like:

#ifndef _WINDOWS_
#define _WINDOWS_
...

So the first time it will go in the file since _WINDOWS_ is not defined, therefore it will be defined and the contents of the file will be included. The second time the #ifndef will fail since the define was done previously.

This is the standard way to put a safeguard, another way which is supported by many compilers is to put #pragma once. This has the advantage to prevent collision in the case someone would define the same constant in another file for example.

Tuesday, August 17, 2021
 
SpiderLinked
answered 4 Months ago
60

Summarizing the comment by Galik and what I realized:

Include guards should be put in every header file in the case that something in the future conflicts. Furthermore, the small time it takes the compiler to process the include guards will make the compilation faster since the extra header does not need to be processed.

Saturday, November 6, 2021
 
PeteT
answered 4 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share