-3

I know that global variables should not be defined in a header and that we should instead use extern to only declare them in the header.

But I've tried nonetheless to define a global variable in the following header lib.h:

//lib.h

int i;

void add();

I got some interesting results when trying to use this header in C and in C++

In C, I included the header in main.c and in lib.c, and it compiles and runs just fine:

//main.c

#include <stdio.h>
#include <stdlib.h>
#include "lib.h"

int main()
{
    printf("%d\n", i);
    add();
    printf("%d\n", i);
    return 0;
}    

----
//lib.c

#include "lib.h"

void add(){
    i++;
}

But when I run it in C++ with similar code (lib.h and lib.cpp are the same as above), it gives an error message about the i variable having multiple definitions:

//main.cpp

#include <iostream>
#include "lib.h"
using namespace std;

int main()
{
    cout<<i<<endl;
    add();
    cout<<i<<endl;
    return 0;
}

Why does it compile in C and not in C++ ?

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Start by eliminating `using namespace std;`, this is exactly the kind of thing it can cause. – Tzalumen Mar 28 '19 at 18:08
  • 1
    @Tzaulmen I tried and it's not working. – Andrei Mihailescu Mar 28 '19 at 18:10
  • Hmm, let me run it through my IDE – Tzalumen Mar 28 '19 at 18:14
  • how are you compiling the two programs? show the compilation command line. also, the variable `i` is uninitialized. – MFisherKDX Mar 28 '19 at 18:18
  • 1
    This is invalid in both languages, but at least gcc (in C mode) allows it by default - what compiler are you using? – aschepler Mar 28 '19 at 18:18
  • 1
    @aschepler Invalid? Why? It is a bad idea, but valid... Did you mean using uninitialized variable? – Eugene Sh. Mar 28 '19 at 18:18
  • @EugeneSh.this throws a compiler error in MSVC14, same as for OP. – Tzalumen Mar 28 '19 at 18:20
  • 1
    @EugeneSh. I don't know about C, but in C++, it violates the one definition rule. – eerorika Mar 28 '19 at 18:20
  • 1
    @EugeneSh. Violation of ODR. In C, it's a tentative definition, but still has external linkage, and each TU will act as though there's a definition with initializer zero. In C++, it's just two definitions, period. – aschepler Mar 28 '19 at 18:21
  • The code is kind of a mess of bad practices though, I believe I know the answer, however. – Tzalumen Mar 28 '19 at 18:22
  • @aschepler Ahh. I didn't notice we have *two* TUs including the header... – Eugene Sh. Mar 28 '19 at 18:22
  • 2
    The header gets copied into the cpp file (that's what #include does). So if you include it in two cpp files, you have just defined two different globals with the same name. That is literally the error message you posted, so ... it's a good error message, and not at all surprising. – Kenny Ostrom Mar 28 '19 at 18:31
  • C++ and C both have one-definition rule. The difference is that C says that if violated behaviour is undefined. **Some** C linkers allow linking together uninitialized variables with same name, see [C11 J.5 Common extensions, J.5.11 Multiple external definitions](http://port70.net/~nsz/c/c11/n1570.html#J.5.11). This does not work for example in MSVC. – Antti Haapala -- Слава Україні Mar 28 '19 at 19:24
  • Strictly, it compiles in both C and C++ - the error is a linker error rather then a compiler error. – Clifford Mar 28 '19 at 19:51
  • 1
    @Tzalumen This has nothing to do with `using namespace std;`. – user253751 Mar 28 '19 at 23:26
  • 1
    @Clifford strictly the C standard does not use the verb *compile* at all, but *translate*; the word compile itself is semantically ambiguous; it is just commonly understood to be a synonym to *translate* in common C environments. C does not even require a linker, it is just said that it is a possibility. – Antti Haapala -- Слава Україні Mar 29 '19 at 05:53
  • @AnttiHaapala : Agreed, but I am referring to the terms and tool chain implementation used by the OP in _this_ question, not the C standard. The distinction has _practical_ rather than _academic_ utility when fixing problems or understanding diagnostics. – Clifford Mar 29 '19 at 07:57

4 Answers4

2

This difference in behavior is not a coincidence, nor a bug in the compilers. It's the strict application of the C and the C++ standards, which diverge on the meaning of having several int i; in the global scope.

In C++ it is invalid

In C++, int i; is a definition of an (uninitialized) object. The One Definition Rule (ODR) does not allow you to define several time the same global variable.

This is defined in the C++ standard, in section [basic.def.odr]

In C it is valid

In C, int i; is a tentative definition. It is perfectly valid to have several tentative declaration of exactly the same global variable.

This is defined in the C11 standard, section 6.9.2 External object definitions:

/2: A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

Note that this clause is worded in a way that doesn't say anything about the case where the same variable is defined in several translation units. The last sentence of the standard quote above does not mean that it's a different variable in each file (for this you'd need internal linkage, with static). It just says that the behavior is as if the initial value of the variable would be 0.

This neutrality has a reason:

  • The standard identifies the case as undedefined behavior:

    Annex J.2: An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier

  • But the standard also identifies the case with multiple definitions as a common extension that is widely supported, as long as these definitions do not contradict each other:

    Annex J.5.11: There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined

Important advice

For this reason, if you intend to write portable code, I recommend strongly to use extern in the header and define the value in one and only one of the compilation unit. This is safe, clear, unambiguous, and works in C as well as C++.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    This has 2 definitions in 2 different files which has undefined behaviour in C but works as a common extension. Neither does `int i;` have internal linkage. – Antti Haapala -- Слава Україні Mar 28 '19 at 19:00
  • Indeed it's not internal linkage, my fault. I've edited and added the quote that shows that it's perfectly valid to have several tentative definition in several translation units. – Christophe Mar 28 '19 at 19:09
  • @LightnessRacesinOrbit this is the only answer that talks about C, but it is incorrect in that. See C11 Appendix J.2. which lists: *"An identifier with external linkage is used, but in the program there does not exist **exactly one** external definition for the identifier, [...] (6.9)."* – Antti Haapala -- Слава Україні Mar 28 '19 at 19:27
  • @Christophe That standard wording talks about multiple tentative declarations within the same TU, not across TUs. – Lightness Races in Orbit Mar 28 '19 at 20:04
  • @LightnessRacesinOrbit indeed, the way it is worded does not say anything about multiple files. And Antti is right about J.2. But J.5.11 clarifies that it's a common extension as long as the different definitions do not contradict themselves (which was my understanding and also my experience). I'll edit to wrap up these different aspects. – Christophe Mar 28 '19 at 20:58
  • @AnttiHaapala ok! You're right about J.2 ! But there's also J5.11 which acknowledges that it's a widely supported (although non portable) extension. So I've edited in order to avoid false impressions ;-) – Christophe Mar 28 '19 at 21:19
  • 1
    The point is that when you look into C, then the behaviour of that is undefined. It is in no way distinct from say dereferencing null pointer or dividing by zero, or [writing to string literals](http://port70.net/~nsz/c/c11/n1570.html#J.5.5). You're arguing that it is fine just because it is listed in the common extensions. – Antti Haapala -- Слава Україні Mar 28 '19 at 21:36
  • _"Note that this clause is worded in a way that doesn't say anything about the case where the same variable is defined in several translation units"_ Right, because it's not about that case, and therefore isn't relevant to the case in the question. – Lightness Races in Orbit Mar 29 '19 at 12:00
1

when I run it in C++ with analogous code it gives an error message about the i variable having multiple definitions. Why is that?

The C++ standard says:

[basic.def.odr] Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required.

Both lib.cpp (I assume that is your "analogous" source file in c++) and main.cpp define the global variable int i. As such, the program is ill-formed.

Solution: Only declare the variable in the header. Define in exactly one translation unit:

//lib.h
extern int i; // this declaration is not a definition

//lib.cpp
int i;        // this declaration is     a definition
eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Not sure why it's working in C but it's wrong in both C and C++. Try:

// lib.h
extern int i;
void add();
// lib.c or lib.cpp
#include "lib.h"
int i = 0;
void add()
{
  ++i;
}
Mike
  • 67
  • 5
-2

So, here's the tricky thing about the preprocessor: it copies and pastes when you use a #define. Which means is that the int i; that main.cpp sees is not the same int i; that lib.cpp sees.

Tzalumen
  • 652
  • 3
  • 16