Header Guards and LNK4006
Asked Answered
A

5

11

I have a character array defined in a header

//header.h
const char* temp[] = {"JeffSter"};

The header if #defined guarded and has a #pragma once at the top. If this header is included in multiple places, I get an LNK4006 - char const * * temp already defined in blahblah.obj. So, I have a couple of questions about this

  1. Why does this happen if I have the guards in place? I thought that they prevented the header from being read in after the first access.
  2. Why do the numerous enums in this header not also give the LNK4006 warnings?
  3. If I add static before the signature, I don't get the warning. What are the implications of doing it this way.
  4. Is there a better way to do this that avoids the error, but lets me declare the array in the header. I would really hate to have a cpp file just for an array definition.
Arbor answered 20/1, 2010 at 23:2 Comment(0)
F
15

Why does this happen if I have the guards in place? I thought that they prevented the header from being read in after the first access.

Include guards make sure that a header is included only once in one file (translation unit). For multiple files including the header, you want the header to be included in each file.

By defining, as opposed to declaring variables with external linkage (global variables) in your header file, you can only include the header in once source file. If you include the header in multiple source files, there will be multiple definitions of a variable, which is not allowed in C++.

So, as you have found out, it is a bad idea to define variables in a header file for precisely the reason above.

Why do the numerous enums in this header not also give the LNK4006 warnings?

Because, they don't define "global variables", they're only declarations about types, etc. They don't reserve any storage.

If I add static before the signature, I don't get the warning. What are the implications of doing it this way.

When you make a variable static, it has static scope. The object is not visible outside of the translation unit (file) in which it is defined. So, in simple terms, if you have:

static int i;

in your header, each source file in which you include the header will get a separate int variable i, which is invisible outside of the source file. This is known as internal linkage.

Is there a better way to do this that avoids the error, but lets me declare the array in the header. I would really hate to have a cpp file just for an array definition.

If you want the array to be one object visible from all your C++ files, you should do:

extern int array[SIZE];

in your header file, and then include the header file in all the C++ source files that need the variable array. In one of the source (.cpp) files, you need to define array:

int array[SIZE];

You should include the header in the above source file as well, to allow for catching mistakes due to a difference in the header and the source file.

Basically, extern tells the compiler that "array is defined somewhere, and has the type int, and size SIZE". Then, you actually define array only once. At link stage, everything resolves nicely.

Feudist answered 20/1, 2010 at 23:17 Comment(0)
J
5

Include guards protect you from including the same header into the same file repeatedly - but not from including it in distinct files.
What happens is that the linker sees temp in more then one object file - you can solve that by making temp static or putting it into an unnamed namespace:

static const char* temp1[] = {"JeffSter"};
// or
namespace {
    const char* temp2[] = {"JeffSter"};
}

Alternatively you can use one source file which defines temp and just declare it as extern in the header:

// temp.cpp:
const char* temp[] = {"JeffSter"};

// header.h:
extern const char* temp[];
Jampan answered 20/1, 2010 at 23:8 Comment(0)
I
5
  1. Header guards have absolutely nothing to do with preventing multiple definitions in your entire program. The purpose of header guards is to prevent multiple inclusion of the same header file into the same translation unit (.cpp file). In other words, they exist to prevent multiple definitions in the same source file. And they do work as intended in your case.

  2. The rule that governs multiple-definition issues in C++ is called One Definition Rule (ODR). ODR is defined differently for different kinds of entities. For example, types are allowed to have multiple identical definitions in the program. They can (and most always have to) be defined in every translation unit where they are used. This is why your enum definition does not result in an error.

    Objects with external linkage are a completely different story. They have to be defined in one and only one translation unit. This is why your definition of temp causes an error when you include the header file into multiple translation units. Include guards can't prevent this error. Just don't define objects with external linkage in header files.

  3. By adding static you give your object internal linkage. This will make the error disappear, since now it is perfectly OK from ODR point of view. But this will define an independent temp object in each translation unit into which your header file is included. To achieve the same effect you could also do

    const char* const temp[] = { "JeffSter" }; 
    

    since const objects in C++ have internal linkage by default.

  4. This depends on whether you need an object with external linkage (i.e. one for the entire program) or an object with internal linkage (unique to each translation unit). If you need the latter, use static and/or extra const (if that works for you) as shown above.

    If you need the former (external linkage), you should put a non-defining declaration into the header file

    extern const char* temp[];
    

    and move the definition into one and only one .cpp file

    char* const temp[] = { "JeffSter" }; 
    

    The above declaration in the header file will work for most purposes. However, it declares temp as an array of unknown size - an incomplete type. If you wish to declare it as an array of known size, you have to specify the size manually

    extern const char* temp[1];
    

    and remember to keep it in-synch between the declaration and definition.

Immediately answered 20/1, 2010 at 23:16 Comment(0)
C
0

I respectfully disagree with the advice against defining variables in headers, because I think "never" is too broad. Nevertheless, the episode that brought me to this thread offers a cautionary tale for those who dare to do so.

I landed on this page as the result of an inquiry into the cause of warning LNK4006, calling out a long established array that I just moved from the translation unit that defines my DLLMain routine into the private header that is included in most of the translation units that comprise this library. I have compiled this library hundreds of times over the last 11 years, and I had never before seen this warning.

Shortly after I read this page, I discovered the cause of the error, which was that the definition was outside the guard block that protects everything else that is defined in the module that also defines DLLMain, which is where I usually gather all the memory blocks that need external linkage. As expected, moving the table inside the guard block eliminated the warnings, leaving me with only two, related to a brand new externally linked table, to be resolved.

Takeaway: You can define variables in headers, and it's a great place to put common blocks, but mind your guards.

Cuirass answered 16/7, 2016 at 9:5 Comment(0)
B
-2

Hang on... you are mixing up your declarations...you did say 'char const * * temp' yet in your header file you have 'const char* temp[] = {"JeffSter"};'.

See section 6.1 of the C FAQ, under 'Section 6. Arrays and Pointers', to quote:

6.1:    I had the definition char a[6] in one source file, and in
    another I declared extern char *a.  Why didn't it work?

A:  In one source file you defined an array of characters and in the
    other you declared a pointer to characters.  The declaration
    extern char *a simply does not match the actual definition.
    The type pointer-to-type-T is not the same as array-of-type-T.
    Use extern char a[].

    References: ISO Sec. 6.5.4.2; CT&P Sec. 3.3 pp. 33-4, Sec. 4.5
    pp. 64-5.

That is the source of the problem. Match up your declaration and definitions. Sorry if this sounds blunt, but I could not help noticing what the linker was telling you...

Bohrer answered 20/1, 2010 at 23:12 Comment(1)
the linker is just translating the array syntax to pointer syntax. Its not an error, its just a warning, the program executes as expected.Arbor

© 2022 - 2024 — McMap. All rights reserved.