The C17 draft states the following (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. [...]
There are 5 storage class specifiers: extern
, static
, _Thread_local
, register
, auto
. Of these, only the former 3 can occur at file scope. Outside of all functions, _Thread_local
can – but needn't be – combined with extern
or static
. Hence, for tentative definitions, there are the following possibilities in terms of storage class specifiers:
int i1; // tentative definition, external linkage
extern int i2; // non-definition declaration, external linkage
static int i3; // tentative definition, internal linkage
_Thread_local int i4; // ?, external linkage
_Thread_local extern int i5; // ?, external linkage
_Thread_local static int i6; // ?, internal linkage
(Note here that the keyword _Thread_local
doesn't influence linkage.)
The declarations of i1
and i3
are tentative definitions. But what about i4
, i5
, i6
– are their declarations tentative definitions? Why or why not?
I am guessing that the answer for i5
is 'no', because neither of the two conditions
static
applies. But I'm unsure about i4
and i6
.
Can a tentative definition use the storage class specifier _Thread_local?
Yes, provided that the declaration in question also includes static
.
The definition of "tentative definition" is:
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.
(C17 6.9.2/2)
_Thread_local
is a storage-class specifier (C17 6.7.1/1), so a declaration that includes _Thread_local
does not satisfy the criterion "without a storage-class specifier". For the most part, a declaration may include at most one storage-class specifier, but _Thread_local
may be combined with either static
or extern
(C17 6.7.1/2), and a declaration that has both _Thread_local
and static
does satisfy the criterion "[or] with the storage-class specifier static
".
Taking your examples one by one, and assuming them to appear at file scope:
int i1;
- no initializer and no storage-class specifier. This is a tentative definition.
extern int i2;
- storage class specifier extern
. This is not a tentative definition.
static int i3;
- no initializer and static
. This is a tentative definition.
_Thread_local int i4;
- _Thread_local
and not static
. This is not a tentative definition, though perhaps that's a defect in the spec.
_Thread_local extern int i5;
- both _Thread_local
and extern
, but not static
. This is not a tentative definition.
_Thread_local static int i6;
- no initializer and static
. This is a tentative definition, though perhaps that's a defect in the spec.
I am guessing [...]
There's really no need here to guess about what the spec says. The wording is clear and simple. You might wonder about whether the spec is defective regarding some of the _Thread_local
cases, but that's a different question. And that leads to ...
The relevant specifications will change in C2X. There, the language is instead:
A declaration of an identifier for an object that has file scope without an initializer, and without the storage-class specifier
extern
orthread_local
, constitutes a tentative definition.
(C2X 6.9.2/2)
In C2X, _Thread_local
is classified as an "alternative spelling" of thread_local
(C2X 6.4.1/3), so it is immaterial that C2X expresses paragraph 6.9.2/2 in terms of thread_local
instead of _Thread_local
. In this version of the language, declarations that include _Thread_local
or thread_local
are expressly excluded from being tentative definitions.
We can suppose, then, that the language in C17 (and C11) that allowed tentative definitions of static thread-locals was probably an oversight, introduced with the _Thread_local
storage-class specifier and the allowance for it to appear together with static
. Having recognized this issue, and the corresponding change in C2X, I would be disinclined to rely on tentative definitions of thread-locals even in conjunction with versions of C that, in principle, permit them.