[regarding two separate .c files, both with "int g;" with no
initializer for either one, that are compiled separately and
then linked]
Well, actually, it did (for him). But as you note:
Tentative definitions are actually useful in a few corner cases,
such as creating a circularly linked list at compile time that
has several elements:
struct foolist {
struct foolist *next, *prev;
... more data here ...
};
static struct foolist foo_head, foo_elem_2, foo_tail;
static struct foo_list foo_head = { &foo_elem_2, &foo_tail, ... };
static struct foo_list foo_elem_2 = { &foo_tail, &foo_head, ... };
static struct foo_list foo_tail = { &foo_head, &foo_elem_2, ... };
In C, you must use tentative definitions to make this work. (For
non-"static" variables, which have external linkage, you can use
"extern" to avoid creating tentative definitions.)
However, as one can see if one has an implementation like the OP's,
this is not quite true:
% cat a.define.c
int x = 0;
% cat b.define.c
int x = 0;
int main(void) { return 0; }
% cc -o define a.define.c b.define.c
/tmp/ccodx0za.o(.data+0x0): multiple definition of `x'
/tmp/ccV8U2Fi.o(.data+0x0): first defined here
% cat a.tent.c
int x;
% cat b.tent.c
int x;
int main(void) { return 0; }
% cc -o tent a.tent.c b.tent.c
%
So, we now have to ask: is this implementation broken? The behavior
differs for tentative definitions and actual (non-tentative)
definitions, despite the above quote from the C standard.
The answer is no, because what the Standard giveth at this point,
the Standard taketh away elsewhere:
K.2 Undefined behavior
[#1] The behavior is undefined in the following circumstances:
[massive snippage]
- 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.
(6.7).
In other words, the implementation can do anything it wants if you
define the same identifier twice (or any other number of times than
1 or 0, with "defined 0 times" being OK only for one particular
case).
The OP's implementation, like mine above, uses this "undefined
behavior" license to provide a "feature" of sorts: tentative
definitions are turned into actual definitions, but not at the end
of each translation unit. Instead, the compiler waits until the
last possible moment -- the "link" phase of compilation -- and only
*then* turns all remaining tentative definitions into actual
definitions. In the process, it saves a bit of disk space. This
feature also lets lazy programmers leave out the keyword "extern".
Many Thanks Richard for replying...
Still i have a doubt related to definition of the variable.Can you
please tell where variable g (variable in the above program) is
defined (space is allocated for the variable)? Is it in a.c or is it
in b.c?
According to the Standard, it is in both. However, your implementation
(like mine) makes use of the undefined behavior to cause it to
happen in *neither*. It happens in a third, "invisible" source
file that your compiler makes up at the last possible moment. (In
my implementation, this is not even an actual file at all, although
some compilers do use an extra file, and even a program called
"collect2", to handle some tricky cases, particularly when mixing
C with other languages. If your linker is smart enough, it can
create a sort of "pretend" file purely in RAM, and avoid the
"collect2" step. This job is a lot easier for pure C programs; it
is only those other langauges that create the need for "collect2".)