C: Extern variable declaration and include guards

554 Views Asked by At

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.

3

There are 3 best solutions below

3
dbush On BEST ANSWER

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.

0
0___________ On

Never define data or functions (except static inline) in the header files. Always do it in the .c source files. If you want to make them visible in other compilation units declare them as extern in the header file.

Guards do not protect you if you include the same .h file in many compilation units which are then linked together.

1
Luis Colorado On

The include guard will protect you of including several times the same include, it will make the definitions in the include to be handled only once, at the first inclusion point. This means the declarations you have made between the protecting marks not be repeated and so don't produce errors about double definition. This is not normally the case of

extern type_of_variable variable_name;

which you can make several times without any complaint from the compiler... it has more to do with type declarations or static functions implementations included in the header.

But why don't you post an example of full compilable code and show what are you trying, and why it doesn't work. From your question I cannot guess what you pretend to do, if something is not working in your case (well, you put references to other cases that probably will have answers, so why don't you use the answers there? what is wrong with the answers given there?)

Please, post a valid example of what is worrying you, and explain precisely why those other posts don't solve your problem (if you have one, at all)

Think that one of the questions doesn't show the actual contents of the repeated include, and doesn't show the actual variable definition. And the other is a question closed 8 years ago, and for some reason it has not been reopened. So you are running the same risk (and I do run the risk of being downvoted for this answer that doesn't actually answer your question, because you don't actually ask something is happening to you -I don't know from your question if you have an actual problem or not).