9

Let's say I have two source files: main.c and a.c:

main.c:

#include <stdio.h>

int a;

int i;
int i;

int main(void)
{
    printf("a = %d\n", a);
    printf("i = %d\n", i);

    return 0;
}

a.c:

int a;

Then, according to latest C99 draft 6.9.2 External object definitions p. 2 (emphasis mine):

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.

Compilation (no warnings given):

gcc -g -std=c99 -pedantic-errors -Wall -Wextra main.c a.c

I understand that for i variable there two are tentative definitions and since main.c does not have "true" external definition they are merged into such one. What about a ? Do I state correctly, that tentative definitions are not "shared" between multiple source files (i.e. translation units) ?

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • Your program is invalid but uses a common extension. See http://stackoverflow.com/questions/1490693/tentative-definitions-in-c99-and-linking?rq=1 – ecatmur Jul 16 '14 at 14:15
  • @ecatmur: Thanks, so there is no _tentative definition_ concept **between** files. Program is invalid because of two external definitions with the same identifier. – Grzegorz Szpetkowski Jul 16 '14 at 14:25

1 Answers1

6

Your program is erroneous: it defines the same external name more than once. The GNU tool chain follows a relaxed linkage model which does not flag this as an error; it merges the multiple definitions. However, that is effectively a language extension. Strictly conforming ISO C programs cannot define a name more than once.

The notion of a "tentative definition" is purely syntactic, within one translation unit. At the end of a translation unit, any definitions which are still tentative are "cemented" as definitions.

The reason int i; is called "tentative" is that it is "weak" in a sense. It can be overridden by a later definition. If, by the end of the translation unit, it isn't then it turns into int i = 0.

So for instance this is valid:

int i;  /* might become int i = 0 */

int i = 42; /* i is now defined; the tentative definition is replaced */

In this situation, i is understood to be defined once, not twice. The translated unit contains a single definition of i.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • Is there any switch to `gcc`, so it follows "strict" mode (i.e. more than one external definition is forbidden) ? – Grzegorz Szpetkowski Jul 16 '14 at 14:22
  • 1
    @GrzegorzSzpetkowski: “strict” mode means: behave undefined, not, that this construct is forbidden (in the sense, that the compiler must complain). And I couldn't find a warning option for Gcc which would warn you. – mafso Jul 16 '14 at 14:46
  • 1
    @mafso: I agree with your point, it's UB, so anything could happen, I found `--warn-common` in `man ld`, that can be passed from `gcc` like `-Wl,--warn-common`. – Grzegorz Szpetkowski Jul 16 '14 at 15:08
  • @mafso (and Grzegorz). The original ANSI C committee struggled with this issue of definitions and linkage. They surveyed existing toolchains (compilers and linkers) and identified several different linkage models. They codified language rules that were broadly consistent with all the models. – Kaz Jul 16 '14 at 15:10
  • 2
    @mafso To read all about this, find the original [*Rationale for ANSI C Programming Language*](http://www.lysator.liu.se/c/rat/title.html), section [3.1.2.2](http://www.lysator.liu.se/c/rat/c1.html#3-1-2-2). – Kaz Jul 16 '14 at 15:11
  • 1
    Another way to get enforcement is via C++, which has a "one definition rule" that is strictly enforced. If you write your code in "Clean C": a cross-dialect between C and C++, then you get extra checks such as type safe linkage and one-definition-rule enforcement when compiling the code as C++. – Kaz Jul 16 '14 at 15:14
  • 1
    For anyone interested I recently found `gcc` option `-fno-common` for disabling relaxed mode extension: "This option prevents global variables being simultaneously defined in different object files (you get an error at link time)." – Grzegorz Szpetkowski Jul 24 '14 at 21:23