clanguage-lawyerdeclaration

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.


Solution

  • 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:

    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.