How are circular #includes resolved?

后端 未结 4 1748
被撕碎了的回忆
被撕碎了的回忆 2020-11-29 12:06

In c lets say we have 2 files

1.h

#include<2.h>

blah blah

and we have 2.h

#include<1.h>

code


        
相关标签:
4条回答
  • 2020-11-29 12:14

    Since you posted your question under the c++ tag as well as c, then I am assuming you are using c++. In c++, you can also use the #pragma once compiler directive:

    1.h:

    #pragma once
    #include "2.h"
    /// rest of 1.h 
    

    2.h:

    #pragma once
    #include "1.h"
    /// rest of 2.h 
    

    The result is the same. But there are some notes:

    1. pragma once will usually compile a little faster since it is a higher level mechanism and doesn't happen at preprocessing like include guards

    2. Some compilers have known bugs "dealing" with #pragma once. Though most if not all modern compilers will handle it correctly. For a detailed list see Wikipedia

    Edit: I think the directive is also supported in c compilers but have never tried it, and besides, in most c programs I've seen, include guards are the standard (maybe due to compiler limitations handling the pragma once directive?)

    0 讨论(0)
  • 2020-11-29 12:19

    Okay, for the sake of completeness I'll begin by quoting tvanfosson's answer:

    You should use include guards:

    // header1.hpp
    #ifndef MYPROJECT_HEADER1_HPP_INCLUDED
    #define MYPROJECT_HEADER1_HPP_INCLUDED
    
    /// Put your stuff here
    
    #endif // MYPROJECT_HEADER1_HPP_INCLUDED
    

    However include guards are not meant to solve circular dependencies issues, they are meant to prevent multiple inclusions, which is quite different.

              base.h
            /        \
        header1.h  header2.h
            \        /
             class.cpp
    

    In this case (quite common), you only want base.h to be included once, and that's what include guards give you.

    So this will effectively prevents the double inclusion... but you won't be able to compile!!

    The problem can be illustrated by trying to reason as the compiler does:

    • Include "header1.hpp" > this defines the include guard
    • Include "header2.hpp
    • Try to include "header1.hpp" but doesn't because the include guard is already defined
    • Cannot correctly parse "header2.hpp" because the types coming from "header1.hpp" have not been defined yet (since it was skipped)
    • Back to "header1.hpp", and the types from "header2.hpp" are still missing since they could not be compiled, and thus it fails here too

    In the end, you'll left with a big pile of error messages, but at least the compiler does not crash.

    The solution is to somehow remove the need for this circular dependency.

    • Use forward declarations if possible
    • Split "header1.h" into 2 parts: the part independent from header2 and the other, you should only need to include the former in header2

    And THIS will solve the circular dependency (manually) there is no magic going on in the compiler that will do it for you.

    0 讨论(0)
  • 2020-11-29 12:29

    Circular inclusions must be eliminated, not "resolved" with include guards (as the accepted answer suggests). Consider this:

    1.h:

    #ifndef HEADER_1_h
    #define HEADER_1_h
    #include "2.h"
    
    #define A 1
    #define B 2
    
    #endif // HEADER_1_h
    

    2.h:

    #ifndef HEADER_2_h
    #define HEADER_2_h
    #include "1.h"
    
    #if (A == B)
    #error Impossible
    #endif
    
    #endif // HEADER_2_h
    

    main.c:

    #include "1.h"
    

    This will throw the "Impossible" error at compile time, because "2.h" fails to include "1.h" due to include guards, and both A and B become 0. In practice, this leads to hard to track errors which appear and disappear depending on the order in which header files are included.

    The right solution here would be to move A and B to "common.h" which then could be included in both "1.h" and "2.h".

    0 讨论(0)
  • 2020-11-29 12:32

    Typically you protect your include file with an ifndef/define that corresponds to the file name. This doesn't prevent the file from being included again, but it does prevent the contents (inside the ifndef) from being used and triggering the recursive includes again.

     #ifndef HEADER_1_h
     #define HEADER_1_h
    
     #include "2.h"
    
     /// rest of 1.h
    
     #endif
    
     #ifndef HEADER_2_h
     #define HEADER_2_h
    
     #include "1.h"
    
     //  rest of 2.h
    
     #endif
    
    0 讨论(0)
提交回复
热议问题