Search code examples
c++compilationlinkerextern

Why is there no duplicate definition error when I define a `const int` in header file?


Apologies if this has been asked before. I searched the internet and did not find an answer.

Say I have a file Common.h, and A.cpp and B.cpp include Common.h.

If I want to have a global const char * in the Common translation unit, I must make it extern in Common.h, and define it in Common.cpp. Otherwise, if I simply define const char * MSG = "Hello World"; in Common.h, I get a duplicate symbol error during compilation.

However, if I simply define a global const int in Common.h with a statement like const int CONSTANT = 10; then the code compiles without a duplicate symbol error, and everything works fine.

I am confused as to why this is the case. It seems to me here that the only difference between the two examples above is the type, which I think shouldn't make a difference. Why do I get the duplicate symbol error for C-strings, but not ints?

Suppose main.cpp, A.h, B.h, A.cpp, and B.cpp look as follows:

// A.h
#pragma once
void f();
// A.cpp
#include "A.h"
#include "Common.h"

#include <iostream>

void f() {
    std::cout << MSG << std::endl;
}
// B.h
#pragma once
void g();
// B.cpp
#include "B.h"
#include "Common.h"

#include <iostream>

void g() {
    std::cout << CONSTANT << std::endl;
}
// main.cpp
#include "A.h"
#include "B.h"

int main()
{
    f();
    g();
}

Now, suppose we take compile with the command g++ main.cpp A.cpp B.cpp Common.cpp -std=c++14.

If we make Common.h and Common.cpp the following, then compilation fails with error duplicate symbol MSG:

// Common.h
#pragma once
const char * MSG = "Hello World";
const int CONSTANT = 10; // defined in header file
// Common.cpp
// empty

However, this compiles:

// Common.h
#pragma once
extern const char * MSG;
const int CONSTANT = 10; // defined in header file
// Common.cpp
#include "Common.h"
const char * MSG = "Hello World";

I am wondering why we need extern and to separate definition & declaration for the string, but not for the int.

Someone suggested making the C-string type as const char * const instead of const char *. Why does making the pointer const work? Also, in this case, what is the difference between this solution and the solution I provided above (where we make the string extern instead and split definition/declaration)? Why do both methods solve the compilation error and what is the difference between the methods?

I noticed also that if I turn const int into just int, then I get the duplicate symbol error again. I feel like the reason behind this is related to the answer to my questions above. Why is this the case?


Solution

  • This is one of the differences of C and C++.

    In C++ a const variable is implicitly static, i.e. only visible to the current translation unit. In C it is implicitly extern so visible to the entire program (which is also the default for the other non-const declararions in both C and C++).

    This explains your observation.

    Note: A const char *p declaration of a variable is not a const variable. It means it points to a const variable (i.e. *p is cannot be modified), but p itself is not const. So behavior here is different. const char * const p would be a const declaration.