Search code examples
cinitializationglobal-variablesc11

Global variable: why "=0" matters?


Scenario:

$ cat t0.c t1.c
/* t0.c */
int i = 12;
/* t1.c */
int i INIT;

int main(void)
{
        return 0;
}

$ gcc t0.c t1.c -DINIT="" -std=c11 -pedantic
<nothing>

$ gcc t0.c t1.c -DINIT="=0" -std=c11 -pedantic
ld: /tmp/ccrTTgwH.o:(.bss+0x0): multiple definition of `i'; /tmp/cckd6R4u.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

Why =0 matters?

UPD:

dvl-linux64 $ gcc82 --version
gcc-8.2 (GCC) 8.2.0

Try gcc 11.2.0:

$ gcc t0.c t1.c -DINIT="" -std=c11 -pedantic
ld: /tmp/ccmPBPUT.o:t1.c:(.bss+0x0): multiple definition of `i'; 
$ gcc t0.c t1.c -DINIT="=0" -std=c11 -pedantic
ld: /tmp/ccxw378s.o:t1.c:(.bss+0x0): multiple definition of `i';

Expected.

Final table:

compiler          -DINIT="" leads to multiple definition of `i'?
gcc 8.2.0         NO
gcc 11.2.0        YES
clang 8.0.1       NO
clang 13.0.0      YES

Solution

  • As far as the C standard is concerned, both are invalid as they constitute multiple external definitions for an identifier. This is actually undefined behavior, however most linkers will throw an error in this situation.

    In the case of int i; in t1.c, this is considered a tentative definition because it has no initializer. This tentative definition however is considered a full definition if there is no other definition in the same translation unit.

    This is described in section 6.9.2p2 of the C standard:

    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.

    However, some compilers such as gcc will take a tentative definition and "join" it with an actual definition in an other object file. Once you have a full definition (i.e. with an initializer) in multiple object files (even if the initializers are the same), then it generates a multiple definition error.

    The -fcommon option enables this behavior and -fno-common disables it in gcc, although the default depends on the version.