11

When I declare a global variable in two different source files and only define it in one of the source files, I get different results compiling for C++ than for C. See the following example:

main.c

#include <stdio.h>
#include "func.h" // only contains declaration of void print();

int def_var = 10;

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

func.c

#include <stdio.h>
#include "func.h"

/* extern */int def_var; // extern needed for C++ but not for C?

void print() {
    printf("%d\n", def_var);
}

I compile with the following commands:

gcc/g++ -c main.c -o main.o
gcc/g++ -c func.c -o func.o
gcc/g++ main.o func.o -o main

g++/clang++ complain about multiple definition of def_var (this is the behaviour I expected, when not using extern). gcc/clang compile just fine. (using gcc 7.3.1 and clang 5.0)

According to this link:

A tentative definition is a declaration that may or may not act as a definition. If an actual external definition is found earlier or later in the same translation unit, then the tentative definition just acts as a declaration.

So my variable def_var should be defined at the end of each translation unit and then result in multiple definitions (as it is done for C++). Why is that not the case when compiling with gcc/clang?

Mike van Dyke
  • 2,724
  • 3
  • 16
  • 31
  • 1
    Possible duplicate of [Extern functions in C vs C++](https://stackoverflow.com/questions/11712707/extern-functions-in-c-vs-c) – Jesper Juhl Mar 27 '18 at 11:21
  • 4
    C and C++ are quite different languages. They don't always have the same semantics for syntactically identical constructs. – Jesper Juhl Mar 27 '18 at 11:22
  • 1
    @JesperJuhl yes, I know they are, but from the link I have posted I expected to behave C like C++ (due to the tentative definition). But it doesn't. That's why I asked – Mike van Dyke Mar 27 '18 at 11:26

2 Answers2

15

This isn't valid C either, strictly speaking. Says as much in

6.9 External definitions - p5

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

You have two definitions for an identifier with external linkage. You violate that requirement, the behavior is undefined. The program linking and working is not in opposition to that. It's not required to be diagnosed.

And it's worth noting that C++ is no different in that regard.

[basic.def.odr]/4

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. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see [class.ctor], [class.dtor] and [class.copy]). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.

Again, a "shall" requirement, and it says explicitly that no diagnostic is required. As you may have noticed, there's quite a bit more machinery that this paragraph can apply to. So the front ends for GCC and Clang probably need to work harder, and as such are able to diagnose it, despite not being required to.

The program is ill-formed either way.


As M.M pointed out in a comment, the C standard has an informative section that mentions the very extension in zwol's answer.

J.5.11 Multiple external definitions

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 (6.9.2).

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    @nos - tentative definitions apply in one translation unit. This is two different units. – StoryTeller - Unslander Monica Mar 27 '18 at 11:31
  • 6.9.2p2 says "If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier" - This, to my reading, appears to allow external definition elsewhere to become part of tentative definition? – P.P Mar 27 '18 at 11:42
  • @P.P. - *a translation unit* is singular. So if a single translation unit contains several definition, the rule applies. It says nothing about several translation units containing definitions. – StoryTeller - Unslander Monica Mar 27 '18 at 11:43
  • Indeed. It's not so clear `gcc func.c main.c` is a "single TU". See [this](https://stackoverflow.com/q/42262802/1275169). – P.P Mar 27 '18 at 11:45
  • @P.P. - Perhaps. But the OP is clearly building separate objects and then linking. I don't think there's much wriggle room there. – StoryTeller - Unslander Monica Mar 27 '18 at 11:46
  • @P.P. In `gcc func.c main.c`, both in practice (far too much stuff would break otherwise) and in principle (any other reading of the standard is too stretched to contemplate), `func.c` and `main.c` constitute separate translation units. – zwol Mar 27 '18 at 11:47
  • 2
    It might also be worth quoting Annex J.5.11 – M.M Mar 27 '18 at 11:53
  • @StoryTeller I read it as I wirtoe previously ( "gcc func.c main.c"). OP is certainly compiling separately! – P.P Mar 27 '18 at 11:57
  • 1
    Note that compiling with `gcc -fno-common` produces the error `multiple definition of def_var`. This option is useful to avoid cross-platform problems with other compilers. – nwellnhof Mar 27 '18 at 16:51
6

I believe you are observing an extension to C known as "common symbols", implemented by most, but not all, Unix-lineage C compilers, originally (IIUC) for compatibility with FORTRAN. The extension generalizes the "tentative definitions" rule described in StoryTeller's answer to multiple translation units. All external object definitions with the same name and no initializer,

int foo; // at file scope

are collapsed into one, even if they appear in more than one TU, and if there exists an external definition with an initializer for that name,

int foo = 1; // different TU, also file scope

then all of the external definitions with no initializers are treated as external declarations. C++ compilers do not implement this extension, because (oversimplifying) nobody wanted to figure out what it should do in the presence of templates. For GCC and Clang, you can disable the extension with -fno-common, but other Unix C compilers may not have any way to turn it off.

zwol
  • 135,547
  • 38
  • 252
  • 361