Outside of any function, int x;
is a tentative definition, and some compilers and linkers treat them as a sort of “cooperative definition,” where an identifier can be declared this way in multiple files and will result in defining only one object.
C’s rules for external declarations (declarations outside of functions) are a bit complicated due to history—C grew with different people developing and experimenting, rather than by design with the knowledge we have today.
Definitions: int x = 3;
is a definition. It both declares the identifier x
and reserves memory for an int
, and it initializes the int
to 3.
Declarations: extern int x;
is a declaration but not a definition. It declares the identifier x
but does not reserve memory for it.
extern int x;
gives x
external linkage, and so does int x = 3;
if it appears outside of a function. External linkage means, when they appear in different source files, the two instances of the identifier will be linked to refer to the same thing in memory.
The C standard says “there shall be” at most one definition for an identifier with external linkage (C 2018 6.9 5). (If the identifier is used in the program, there must be a definition. If it is not used in an expression, you do not need a definition.)
Tentative definitions: int x;
is a hybrid. Outside of a function, it is a special kind of declaration called a tentative definition. The C standard says that, if there is a tentative definition in a translation unit (the source file being compiled, along with all the files it includes) and there is no regular definition, a regular definition will be created.
Now, what happens if you violate the rule that “there shall be” at most one definition? Here is the thing: It is not a rule a program has to obey. When the C standard says “shall” it means, if a program obeys this rule, the behavior will be as the C standard says. If a program disobeys this rule, the C standard does not define the behavior (C 2018 4 2). Instead, we let the compiler and the linker define the behavior, if their designers elect to do so.
A common behavior (but not the only possibility) in compilers and linkers when a program violates the rule about at most one definition was:
- If there are multiple regular definitions when linking, report an error.
- If there are multiple definitions coming from tentative definitions but no more than one coming from a regular definition, coalesce them into a single definition.
This was the defined default behavior in GCC and associated tools prior to GCC version 10 and was explicitly mentioned in the C 2018 standard’s informative section on common extensions, in J.5.11. In current versions of GCC, multiple definitions of any type are treated as an error by default. You can request the old behavior with the command-line switch -fcommon
.
add.h
file that hasvoid func(void);
andextern int a;
. Then have a#include "add.h"
in yourmain.c
. Global variables are nasty, however. – Irenicsint a;
it is a "common" symbol. See my answer: #64627417 for an explanation – Poshmain.c
andadd.c
with the GCC compiler invoked asgcc -Wall -Wextra -g -c
then link withgcc main.o add.o -o yourprog
then use the GDB debugger onyourprog
executable. Do read documentation of both your compiler and your debugger – Pourboirea
does not mean you can rely on the compiler to reject the program or even diagnose the issue. "Undefined" means "undefined". – Trapshooting