Lover of Structure
Lover of Structure

Reputation: 1858

Can a tentative definition use the storage class specifier _Thread_local?

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

applies. But I'm unsure about i4 and i6.

Upvotes: 2

Views: 74

Answers (3)

Lover of Structure
Lover of Structure

Reputation: 1858

Seeing the other answers, let me take a guess at what happened historically:

  • The notion of a tentative definition was introduced for historical reasons (there are many possible references one could provide, eg: What is the rationale behind tentative definitions in C?).
  • The keyword _Thread_local was introduced with C11 (which C17 is a corrected version of, without substantial additions).
  • If the definition of 'tentative definition' (C17 draft, 6.9.2, ¶2) is taken literally, the answer is clear – though when asking I was wondering whether there are other parts of the standard that encourage a different interpretation or outright contradict the definition, because the wording looks like there was an oversight.
  • If _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

John Bollinger
John Bollinger

Reputation: 181199

Can a tentative definition use the storage class specifier _Thread_local?

In C17

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 ...

In C2X

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 or thread_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

KamilCuk
KamilCuk

Reputation: 141698

what about i4, i5, i6

_Thread_local int i4;
  • not without a storage-class specifier
  • not with the storage-class specifier static
  • not tentative

  _Thread_local extern int i5;
  • not without a storage-class specifier
  • not with the storage-class specifier static
  • not tentative

  _Thread_local static int i6;
  • not without a storage-class specifier
  • with the storage-class specifier static
  • is tentative

Can a tentative definition use the storage class specifier _Thread_local?

Sure.

Upvotes: 2

Related Questions