Reputation: 1858
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
.
Upvotes: 2
Views: 74
Reputation: 1858
Seeing the other answers, let me take a guess at what happened historically:
_Thread_local
was introduced with C11 (which C17 is a corrected version of, without substantial additions)._Thread_local
was orthogonal to the notion of a "tentative definition", i4
and i6
would be tentative definitions, while i5
would not.Let me summarize everything in a table:
Tentative definition? | C17 draft, 6.9.2 ¶2; literal interpretation |
C17 draft, 6.9.2 ¶2; modified to assume that _Thread_local is orthogonal to the definition |
C23 draft (n3096.pdf ),6.9.2 ¶2 |
---|---|---|---|
int i1; |
✓ | ✓ | ✓ |
extern int i2; |
✗ | ✗ | ✗ |
static int i3; |
✓ | ✓ | ✓ |
_Thread_local int i4; |
✗ | ✓ | ✗ |
_Thread_local extern int i5; |
✗ | ✗ | ✗ |
_Thread_local static int i6; |
✓ | ✓ | ✗ |
The left-hand column indicates what some of our language lawyers argue for regarding C17 – I acknowledge this as a reasonable view. The middle column represents one possible interpretation I suspected when formulating my question, namely that _Thread_local
has no bearing on the issue. The right-hand column represents what C23 will likely be like.
Given what the C23 (or C2x) standard draft n3096.pdf
says, it is safe to assume that the C17 standard was carelessly edited/worded in this regard.
As for why, please allow me to speculate: Originally different compilers handled multiple declarations/definitions differently, which necessitated the notion of a "tentative definition" in the standard. 'Thread-local' storage duration was introduced so long after this that C23 perhaps makes the reasonable assumption that the _Thread_local
storage class specifier appears mainly in code that doesn't rely on tentative definitions. Or the standards committee just wanted to make life easier for compiler writers.
Upvotes: 1
Reputation: 181199
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.
Upvotes: 3
Reputation: 141698
what about i4, i5, i6
_Thread_local int i4;
without a storage-class specifier
with the storage-class specifier static
_Thread_local extern int i5;
without a storage-class specifier
with the storage-class specifier static
_Thread_local static int i6;
without a storage-class specifier
with the storage-class specifier static
Can a tentative definition use the storage class specifier _Thread_local?
Sure.
Upvotes: 2