Well yeah I said to use one initializer.
That's not enough. Initializers do not matter, actually.
If you have several translation units with
int a; /* no initializer */
in them, your code is already violating the One Definition Rule.
The above is a tentative definition. However, once the translation unit ends, it will implicitly produce a regular definition, equivalent to
int a = 0;
(see http://port70.net/~nsz/c/c11/n1570.html#6.9.2p2 (http://port70.net/~nsz/c/c11/n1570.html#6.9.2p2)). Each translation unit will produce a regular definition of `a`, and you will end up with multiple definitions of `a` in the program, thus violating ODR.
However, some C implementations allow this. That's exactly the extension J.5.11 is talking about.
A lot of people fall into the "tentative definition" trap. They believe that just because the above is a tentative definition, they can have as many of those as they want in their entire program. That's not true. Tentative definition is a local concept, local to each translation unit. Thanks to tentative definitions you can have this
int a;
int a;
int a, a, a, a, a;
in one translation unit. The compiler proper shall not complain.
However, once the compiler gets to the end of the translation unit, it is required to convert these tentative definitions into a regular definition, i.e. emit a single regular definition of `a` for this translation unit.
And if you have multiple translation units with tentative definitions of `a`, each of them will emit a regular definition of `a`. This violates the standard ODR. The linker is supposed to complain.
tentative definitions are a bitch anyway and very bad style IMO. Don't do that. They can only confuse anyone reading the code, for very rarely a good reason.
Not sure what you mean here.
When people want to provide a definition for a global variable in C, they often just do
int global;
in some (one and only one) translation unit. They know that the variable will be zero-initialized by default and they are happy with that. End of story.
But formally, this is a tentative definition.
It is not like these people wanted to use a tentative definition specifically. They just wanted to define a variable. It works the way they want to, and that is all they care about. Overwhelming majority of them haven't even heard about "tentative definitions".
So, what do you mean by tentative definitions' being "a bitch"? How would the above "confuse" anyone?
The only case where you are actually using tentative definitions in any material way is when you do
int global;
...
int global;
i.e. define the same variable many times in the same translation unit. But normally nobody does that. If only by mistake...
---
Come to think of it, my attempts to come up with a good non-contrived material use case for tentative definitions come up empty. Maybe someone here knows a good one?
Re-reading the C99 Rationale it appears that the feature was introduced to support some ancient pre-standard declaration-defintion approaches for `static` variables. I.e. it is a feature that simplifies support of non-standard legacy code. Any material use is redundant in standard code. Which is why I can't come up with an example.
One thing that has not been discussed in great detail is the issue surrounding the static initialisation order fiasco (https://en.cppreference.com/w/cpp/language/siof).
In short, if you have global variables interdependent across multiple translation units (or implementation files), then their values will be undefined.
The most reliable way to work around this issue is to construct the variable (with global semantics) on first use. Toy example:
// global.h
int universalConstant();
// global.c
int universalConstant()
{
static int value = 42;
return value;
}