简体   繁体   中英

When to use include guards?

I know that the use of include guards in header files is to prevent something from being defined twice. Using this code sample though, was completely fine:

foo.c

#include <stdio.h>
#include <string.h>
#include "bar.h"

int main() {
    printf("%d", strlen("Test String"));
    somefunc("Some test string...");
    return 0;
}

bar.h

#ifndef BAR_H_INCLUDED
#define BAR_H_INCLUDED
void somefunc(char str[]);
#endif

bar.c

#include <stdio.h>
#include <string.h>
#include "bar.h"

void somefunc(char str[]) {
    printf("Some string length function: %d", strlen(str));
}

The above snippets are compiled with gcc -Wall foo.c bar.c -o foo and there is no error. However, both <stdio.h> and <string.h> were included without an include guard. There is still no error when I strip bar.h down to the single statement void somefunc(char str[]); . Why is there no error?

Firstly, the primary purpose of include guards is to prevent something from being declared twice in the same translation unit . The "in the same translation unit" part is the key here. Your experiment with two different translation units has nothing to do with the purpose of include guards. It does not demonstrate anything. It is not even remotely related.

In order to take advantage of include guards you have to include (explicitly or implicitly) the same header file twice into one implementation file.

Secondly, just because some header file has no include guards and just because you included that header file twice into the same translation unit does not mean that it will necessarily trigger errors. In order to lead to errors the header must contain declarations of specific "non-repeatable" kind. No every header contains such offending declarations. Not every declaration is offending in this sense.

Your bar.h (as posted) is actually harmless. Formally, you don't need include guards in your bar.h . It has a single function declaration, which can be repeated many times in one translation units. So, including this header multiple times will not lead to errors.

But add something like that to your bar.h

struct SomeStruct
{
  int i;
};

and then just include it twice in the same implementation file, and you will end up with an error. This error is what include guards are intended to prevent. The language prohibits repeating full declarations of the same struct type in the same translation unit.

Include guards are typically placed in header files unconditionally. They are, I'm quite sure, present inside <stdio.h> and <string.h> as well. It is unclear why you claim that these headers "were included without an include guard". Did you check inside these files? In any case, again, your experiment with two different translation units does not demonstrate anything relevant anyway.

Duplicate declarations aren't a problem; duplicate (type) definitions are. If the bar.h header contained, for example:

enum FooBar { FOO, BAR, BAZ, QUX };

then including that twice in a single TU (translation unit — source file plus included headers) would give an error in general.

Also, the multiple inclusion scenario isn't what you show. What might cause the trouble is the following, assuming that there are no header guards in the header files:

  • bar.h

     enum FooBar { FOO, BAR, BAZ, QUX }; void somefunc(char str[]); 
  • quack.h

     #include "bar.h" extern enum FooBar foobar_translate(const char *str); 
  • main.c

     #include "bar.h" #include "quack.h" … 

Note that GCC has an option -Wredundant-decls to identify redundant declarations — where the same declaration is present several times (normally from multiple files, but also if the same declaration is present twice in a single file).

Prior to C11, you could not repeat a typedef (at file scope; you always could hide an outer typedef in a block scope). C11 relaxes that constraint:

§6.7 Declarations

¶3 If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except that:

  • a typedef name may be redefined to denote the same type as it currently does, provided that type is not a variably modified type;
  • tags may be redeclared as specified in 6.7.2.3.

However, you still can't define a structure type twice in a single scope of a TU, so notations such as:

typedef struct StructTag { … } StructTag;

must be protected with header guards. You don't have this problem if you use opaque (incomplete) types:

typedef struct StructTag StructTag;

As to why you can include standard headers, that's because the standard requires that you can:

§7.1.2 Standard headers

¶4 Standard headers may be included in any order; each may be included more than once in a given scope, with no effect different from being included only once, except that the effect of including <assert.h> depends on the definition of NDEBUG (see 7.2). If used, a header shall be included outside of any external declaration or definition, and it shall first be included before the first reference to any of the functions or objects it declares, or to any of the types or macros it defines. However, if an identifier is declared or defined in more than one header, the second and subsequent associated headers may be included after the initial reference to the identifier. The program shall not have any macros with names lexically identical to keywords currently defined prior to the inclusion of the header or when any macro defined in the header is expanded.

Using header guards allows you to make your headers meet the same standard that the standard headers meet.

See also other diatribes (answers) on the general subject, including:

The reason there is no error in your code is that your header file is declaring but not defining somefunc() . Multiple declarations of something are fine, as long as they are not definitions - the compiler can accept seeing something declared more than once (as long as the declarations are compatible, of course).

Generally speaking, include guards are needed to avoid circular dependencies between header files, such as

  • Header A and header B mutually include each other in some situations. An include guard is needed in at least one of the headers to prevent infinite looping in the preprocessor.
  • Header A includes header B because it depends on a definition (eg of an inline function, a typedef ) within it, but other header files OR compilation units may include header A, header B, or both in different circumstances. Include guards are needed to prevent multiple definitions in compilation units that include both those headers. At a minimum, the header file containing the definitions needs to have an include guard.

Since header files can include each other in any order, problems like those addressed above can become quite complicated.

Preventing some types of multiple definition is a side-effect of the above, but is not the primary purpose of include guards.

Not withstanding all the above, the rule of thumb I work by with header files is "use include guards in all header files unless I have a particular reason not to". By doing that, all the potential problems associated with not providing include guards are avoided. The circumstances in which it is necessary to avoid an include guard (such as the header declaring or defining different things, dependent on a macro being defined/undefined in the compilation unit) are relatively rare in practice. And, if you are using such techniques which require such things, you should already know that you should not be using include guards in the affected headers.

Why? Because:

int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);
int char_to_int(char* value, int *res);

int char_to_int(char* value, int *res)
{
    // do something
}

Function prototypes do not error it they are the same and the function

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM