30

Consider following program. Will this give any compilation errors?

#include <stdio.h>
int s=5;
int s;
int main(void)
{
     printf("%d",s);
}

At first glance it seems that compiler will give variable redefinition error but program is perfectly valid according to C standard. (See live demo here http://ideone.com/Xyo5SY).

A tentative definition is any external data declaration that has no storage class specifier and no initializer.

C99 6.9.2/2

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.

My question is, what is rationale for allowing tentative definitions? Is there any use of this in C? Why does C allow tentative definitions?

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Destructor
  • 14,123
  • 11
  • 61
  • 126
  • 2
    I don't think there're any valuable reasons for that because C++ itself has never had it, among many other programming languages. – edmz Oct 18 '15 at 18:51
  • @edmz C++ is not C, nor have the two languages (even in their pre-standardized C with classes and K&R C forms) ever been compatible, so C++ has zero place in a discussion about the ANSI C standard. The reason for tentative definitions is the same reason you can still define C functions with K&R syntax (`main(c,v)int c; char **v;{ ... }`): backward-compatibility. *Actual* backward-compatibility. As in, you can run a C codebase that hasn't been touched since 1973 through a modern compiler and it will still compile. – Braden Best May 01 '21 at 20:00

2 Answers2

17

Tentative definitions was created as a way to bridge incompatible models that existed pre-C89. This is covered in the C99 rationale section 6.9.2 External object definitions which says:

Prior to C90, implementations varied widely with regard to forward referencing identifiers with internal linkage (see §6.2.2). The C89 committee invented the concept of tentative definition to handle this situation. A tentative definition is a declaration that may or may not act as a definition: If an actual definition is found later in the translation unit, then the tentative definition just acts as a declaration. If not, then the tentative definition acts as an actual definition. For the sake of consistency, the same rules apply to identifiers with external linkage, although they're not strictly necessary.

and section 6.2.2 from the C99 rationale says:

The definition model to be used for objects with external linkage was a major C89 standardization issue. The basic problem was to decide which declarations of an object define storage for the object, and which merely reference an existing object. A related problem was whether multiple definitions of storage are allowed, or only one is acceptable. Pre-C89 implementations exhibit at least four different models, listed here in order of increasing restrictiveness:

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 1
    How you know & understand C & C++ standard so well? You are really genius. – Destructor Oct 25 '15 at 16:36
  • 2
    @PravasiMeet I know it well because I spend a lot of time reading the standard and the related documents and SO questions. It is just about practice and experience. The more experience you have, the more interesting problems you will get to solve and it just builds from there. – Shafik Yaghmour Oct 26 '15 at 19:24
  • I don't think the second quote is really relevant. Tentative definitions is a feature local to one translation unit. It is a purely *compiler proper* feature, no linker involved. It has no relation to external linkage and the accompanying Ref/Def models. I don't see the Rationale in 6.2.2 drawing any connections between the Ref/Def models and tentative definitions. – AnT stands with Russia Nov 11 '21 at 07:27
6

Here's an example of a case where it's useful:

void (*a)();

void bar();
void foo()
{
    a = bar;
}

static void (*a)() = foo;

/* ... code that uses a ... */

The key point is that the definition of foo has to refer to a, and the definition of a has to refer to foo. Similar examples with initialized structures should also be possible.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 3
    In this specific case, you could avoid the need for a tentative declaration by adding an `extern` to the first line (making it just a declaration). Where you really need the tentative declaration is if you want `a` to be `static` (file scope) – Chris Dodd Oct 18 '15 at 17:29
  • @ChrisDodd: Yes, I missed that. I'll change it. Thanks. – R.. GitHub STOP HELPING ICE Oct 18 '15 at 17:32
  • 4
    Another way to avoid this would be `void foo(); void (*a)() = foo;` – M.M Oct 23 '15 at 04:43
  • `error: static declaration of 'a' follows non-static declaration` – pmor Aug 28 '22 at 08:19
  • Possibly one more example where it is useful: `struct x x; struct x { char x[ sizeof( &x ) ]; };`. Note: MSVC fails to compile this. Note: MSVC team won't fix it. – pmor Aug 28 '22 at 08:42