weird header behavior in gcc and llvm

Started by
8 comments, last by Bregma 9 years, 6 months ago

I have a typical c++ project going where you end up with a .h and .ccp file for every class.

I have these files arranged in directories on disk with one h.h file including the others in the directory.

The files in the directory include the single .h file, which includes the next level up's file.

Basically its one big tree structure of included files.

I use header guards in every .h file.

For example:

main.cpp includes core/core.h. core.h includes network/network.h (as well as other directories). network.h includes ../core.h and subdirectories like records/records.h. records.h include ../network.h and the .h files in the directory.

99.99% of the time I have no problems at all. Occasionally I have a random cpp file that when compiled can't find something defined in a .h file even when I include it directly (typically I just include one .h file in all of my .cpp files). It behaves like the header guard is already defined causing it to skip the contents, but I triple verified that it is not. Many times though including the root .h file in the troublesome cpp file resolves the issue.

Are there some quirks in "#ifndef my_file_h #define my_file_h" that cause it to fail in gcc and llvm?

Whats really maddening though is if I comment out the header guard it will complain about it being defined twice instead of not being defined at all.

--and it always works fine in all of the other cpp files, just not that one (every .h file is technically included in every .cpp file because of the bidirectional tree hierarchy of includes).

Advertisement

When you say "random" do you mean it's always the same cpp file, i.e. rebuilding does not fix it, and when it's fixed it doesn't come back? If it's deterministic, then it looks to me like you may just have a duplicate header guard that you missed, or a typo between the #ifndef and the #define in one of them. That can really mess you up. So check carefully.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

With GCC and probably also Clang, you can pass a flag to keep intermediate files such as the preprocessed .i files.

I think it's something like "-S -save-temps" for GCC.

[Edit] And then you can inspect the .i or .ii file from your problematic .cpp and figure out what the preprocessor spits out. There are also a couple of other flags which lets you keep varying amounts of defines etc in your files and see their end values.

It sounds like you have a circular #include. The order of includes is what causes this to manifest as trouble or not.


every .h file is technically included in every .cpp file because of the bidirectional tree hierarchy of includes

This is a really bad idea. This means your entire project needs to be built every time any header file changes. Generally, the advice is to be minimal with what you #include. If you can, use forward declarations rather than #including a given header. There are design aspects to this, if your design is loosely coupled it will be easier to avoid #including certain headers.


Generally, the advice is to be minimal with what you #include. If you can, use forward declarations rather than #including a given header.

How do forward declarations help exactly? Won't you end up (re)declaring most (if not all) of the classes and functions in the header? Isn't that a lot of code duplication? Won't that cause problems if you modify the declaration in the header file but forget to modify it the same way everywhere it's forward declared?

As a Java guy this has been one of the hardest things to adapt to when writing C/C++. Just figuring out the order that things should be declared, and whether they should be in the .h or the .cpp file is hard sometimes.


Generally, the advice is to be minimal with what you #include. If you can, use forward declarations rather than #including a given header.

How do forward declarations help exactly? Won't you end up (re)declaring most (if not all) of the classes and functions in the header? Isn't that a lot of code duplication? Won't that cause problems if you modify the declaration in the header file but forget to modify it the same way everywhere it's forward declared?

What you are talking about is definition, not declaration. This is a forward declaration of a class that you should, when possible, put in your files instead of including the corresponding full header file.


class foo; // introduce the name foo as a class to the compiler, but don't define it yet

The definition then goes in the header file as usual, and you only include this file where you absolutely need it.


class foo {
    ...
};

You are free to change the definition as much as you like, the declarations don't change.

There are many things you can do with a declaration only, such as defining pointers and references, and function arguments. Only once you dereference, allocate or instantiate an object of the class, or otherwise need access to the internals of the class, do you need the full definition.

When you say "random" do you mean it's always the same cpp file, i.e. rebuilding does not fix it, and when it's fixed it doesn't come back? If it's deterministic, then it looks to me like you may just have a duplicate header guard that you missed, or a typo between the #ifndef and the #define in one of them. That can really mess you up. So check carefully.

Its always the same file but the reason behind which file causes this problem to trip up is random.


Generally, the advice is to be minimal with what you #include. If you can, use forward declarations rather than #including a given header.

How do forward declarations help exactly? Won't you end up (re)declaring most (if not all) of the classes and functions in the header? Isn't that a lot of code duplication? Won't that cause problems if you modify the declaration in the header file but forget to modify it the same way everywhere it's forward declared?

What you are talking about is definition, not declaration. This is a forward declaration of a class that you should, when possible, put in your files instead of including the corresponding full header file.


class foo; // introduce the name foo as a class to the compiler, but don't define it yet

The definition then goes in the header file as usual, and you only include this file where you absolutely need it.


class foo {
    ...
};

You are free to change the definition as much as you like, the declarations don't change.

There are many things you can do with a declaration only, such as defining pointers and references, and function arguments. Only once you dereference, allocate or instantiate an object of the class, or otherwise need access to the internals of the class, do you need the full definition.

In this particular .h file that is throwing out errors because a base class is not defined (although it is declared probably in another header) and this time its being tripped up in main.cpp strangely. Usually when i have this problem its in some .cpp file later on in the project.

Usually everything works fine and then an already existing and tested .h file will throw not defined errors when i add more sources to the project.

In most cases I just use pointers to get around this since you can declare a class as many times as you want. This time its a base class of a subclass thats not defined so no easy work arounds.

I think maybe I'm one of the few people who use headers this way which means I'm finding quirks that most people will never see.

It sounds like you have a circular #include. The order of includes is what causes this to manifest as trouble or not.


every .h file is technically included in every .cpp file because of the bidirectional tree hierarchy of includes

This is a really bad idea. This means your entire project needs to be built every time any header file changes. Generally, the advice is to be minimal with what you #include. If you can, use forward declarations rather than #including a given header. There are design aspects to this, if your design is loosely coupled it will be easier to avoid #including certain headers.

I'm only working on the network client-server code so its fairly tightly coupled. Most classes only need to have pointers to other classes though.

I think that maybe I need to change how my headers are included in the cpp files. Maybe if I include at the lowest split in my header hierarchy that has all headers needed in the cpp file it may work.

I do use header guards extensively so even though header files are circularly referenced the second time the compiler hits the top of the same header file it should bypass everything in the guard (i throw my include statements inside of the guard as well).

I started doing my headers this way because I was tired of header hell. You know how it is when you need to type 20 include statements.


Maybe if I include at the lowest split in my header hierarchy that has all headers needed in the cpp file it may work.

That made it work. Weirdness.

Maybe the preprocessor is having brain farts after seeing the same #ifndef #define statements a few dozen times.

Header guards do exactly zero to help with circular includes. In fact, they are causing the problem in the first place (kind of, otherwise you'd get endless recursion and are still screwed). You use them to prevent getting multiple declarations of the same stuff for one source file. You have the opposite problem where your circular includes result in one header trying to use something from the other header before it was ever declared (no matter which order you include them).

A says "copy everything from B to here, because I need it declared before my stuff", then B says "copy everything from A to here, because I need it declared before my stuff". But A already declared the header guards and nothing is included. You end up with a file in the order B - A - source. B is trying to use stuff from A that is only declared further down and you get errors.

The two steps when you have circular includes is a) ask yourself if you screwed up the design when you introduced circular dependencies in the first place and if your answer is "no" then b) break them with forward declarations.

Okay, technically you should prefer forward declarations anyway (at least if you just need a single class or two... but then, if you find yourself needing declarations for dozens of functions from somewhere else in a header file, you probably got bigger problems).

f@dzhttp://festini.device-zero.de

Here's a tip.

Physical dependencies should always be a DAG (directed acyclic graph). Always.

One aspect of that is that lower-level things should never depend on higher-level things. A source file down the hierarchy should never depend on one up the same hierarchy. Same goes with link-time dependencies (libraries, etc). Directed acyclic graphs. Only.

Using forward declarations is one of the tools given by the designer of C to allow you to have cyclic logical dependencies without having cyclic physical dependencies. Logical coupling should be minimized: physical coupling even more so.

Also, header files should only contain the public interface of a module. The should expose only what needs to be exposed, no more. They should also be idempotent: they should include everything they must include, and no more, and be included in any arbitrary order.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement