Search code examples
cincludeinclude-guards

C: Extern variable declaration and include guards


I've had the same problem as described in these two posts (First and Second) regarding declaration of variables in header files. The solution listed works well for me, but nonetheless I have a basic question on the solution:

Why would an include guard still not solve this issue? I would expect that the include guard would avoid my variable to be declared multiple times if I include the same header file multiple times.


Solution

  • Include guards are useful for preventing multiple delcarations or type definitions in a single translation unit, i.e. a .c file that is compiled by itself along with all of the headers it includes.

    Suppose you have the following headers without include guards:

    a.h:

    struct test {
        int i;
    };
    
    struct test t1;
    

    b.h:

    #include "a.h"
    
    struct test *get_struct(void);
    

    And the following main file:

    main.c:

    #include <stdio.h>
    #include "a.h"
    #include "b.h"
    
    int main()
    {
        struct test *t = get_struct();
        printf("t->i=%d\n", t->i);
        return 0;
    }
    

    When the preprocessor runs on the, the resulting file will look something like this (neglecting the contents of stdio.h):

    struct test {
        int i;
    };
    
    struct test t1;
    
    struct test {
        int i;
    };
    
    struct test t1;
    
    struct test *get_struct(void);
    
    int main()
    {
        struct test *t = get_struct();
        printf("t->i=%d\n", t->i);
        return 0;
    }
    

    Because main.c includes a.h and b.h, and because b.h also includes a.h, the contents of a.h appear twice. This causes struct test to be defined twice which is an error. There is no problem however with the variable t1 because each constitutes a tentative definition, and multiple tentative definitions in a translation unit are combined to refer to a single object defined in the resulting main.o.

    By adding include guards to a.h:

    #ifndef A_H
    #define A_H
    
    struct test {
        int i;
    };
    
    struct test t1;
    
    #endif
    

    The resulting preprocessor output would be:

    struct test {
        int i;
    };
    
    struct test *get_struct(void);
    
    int main()
    {
        struct test *t = get_struct();
        printf("t->i=%d\n", t->i);
        return 0;
    }
    

    Preventing the duplicate struct definition.

    But now let's look at b.c which constitutes a separate translation unit:

    b.c:

    #include "b.h"
    
    struct test *get_struct(void)
    {
        return &t1;
    }
    

    After the preprocessor runs we have:

    struct test {
        int i;
    };
    
    struct test t1;
    
    struct test *get_struct(void);
    
    struct test *get_struct(void)
    {
        return &t1;
    }
    

    This file will compile fine since there is one definition of struct test and a tentative definition of t1 gives us an object defined in b.o.

    Now we link a.o and b.o. The linker sees that both a.o and b.o contain an object called t1, so the linking fails because it was defined multiple times.

    Note here that while the include guards prevent a definition from appearing more than once in a single translation unit, it doesn't prevent it from happening across multiple translation units.

    This is why t1 should have an external declaration in a.h:

    extern struct test t1;
    

    And a non-extern declaration in one .c file.