为什么必须 __thread 跟随 extern 或 static

Why must __thread follow extern or static

我正在阅读 Kerrisk's book 并看到以下内容作为 31-4 的注释,

  • The __thread keyword must immediately follow the static or extern keyword, if either of these is specified in the variable’s declaration.
  • The declaration of a thread-local variable can include an initializer, in the same manner as a normal global or static variable declaration.
  • The C address (&) operator can be used to obtain the address of a thread-local variable.

我想知道关键字必须在staticextern后面的原因。没有它们就不能使用吗?

其示例代码,

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* strerror_tls.c

   An implementation of strerror() that is made thread-safe through
   the use of thread-local storage.

   See also strerror_tsd.c.

   Thread-local storage requires: Linux 2.6 or later, NPTL, and
   gcc 3.3 or later.
*/
#define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
                                       declarations from <stdio.h> */
#include <stdio.h>
#include <string.h>                 /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"


#define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
                                       buffer returned by strerror() */
/*   ||||||||||||||||||
//   vvvvvvvvvvvvvvvvvv
*/
    static     __thread      char buf[MAX_ERROR_LEN];
/* Thread-local return buffer */


char *
strerror(int err)
{
    if (err < 0 || err >= sys_nerr || sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '[=11=]';          /* Ensure null termination */
    }

    return buf;
}

static void *
threadFunc(void *arg)
{
    char *str;

    printf("Other thread about to call strerror()\n");
    str = strerror(EPERM);
    printf("Other thread: str (%p) = %s\n", str, str);

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t;
    int s;
    char *str;

    str = strerror(EINVAL);
    printf("Main thread has called strerror()\n");

    s = pthread_create(&t, NULL, threadFunc, NULL);
    if (s != 0)
        errExitEN(s, "pthread_create");

    s = pthread_join(t, NULL);
    if (s != 0)
        errExitEN(s, "pthread_join");

    /* If strerror() is not thread-safe, then the output of this printf() be
       the same as that produced by the analogous printf() in threadFunc() */

    printf("Main thread:  str (%p) = %s\n", str, str);

    exit(EXIT_SUCCESS);
}

重读你引用的文字(强调我的):

  • The __thread keyword must immediately follow the static or extern keyword, if either of these is specified in the variable’s declaration.

因此,如果两者均未指定,则此条款不适用。

至于"why",如果这个说法是正确的(我怀疑它大致是正确的但表述不准确),那只是"GNU C"的语法问题,其中__thread从。您可以在 C11 标准中查找等效 _Thread_local 的语法要求。

线程局部变量的标准 C 存储 class 说明符是 _Thread_local. The standard also says in §6.11 Future directions:

6.11.5 Storage class specifiers

The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature.

因此,标准说存储 class 关键字(staticexternauto — 不要使用它! — register —同上 — _Thread_localtypedef) 应该 出现在声明的开头。在出现 staticextern_Thread_local 的情况下,书中的建议是 staticextern 应该放在第一位,而 _Thread_local 第二位。

当然,本书使用的是 __thread 而不是 _Thread_local。这是一个特定于编译器(实现)的关键字,其行为类似于标准 C _Thread_local 和 Microsoft 的 __declspec(thread).

关于 thread local storage 文档的 GCC 文档(强调已添加):

At the user level, the extension is visible with a new storage class keyword: __thread. For example:

__thread int i;
extern __thread struct state s;
static __thread char *p;

The __thread specifier may be used alone, with the extern or static specifiers, but with no other storage class specifier. When used with extern or static, __thread must appear immediately after the other storage class specifier.

The __thread specifier may be applied to any global, file-scoped static, function-scoped static, or static data member of a class. It may not be applied to block-scoped automatic or non-static data member.

因此,您看到的是 GCC 特定于线程本地存储的表示法,正如我所指出的和 GCC 手册所指出的那样,存储 class 信息应该在声明中排在第一位(并且GCC 在 staticextern 之后明确表示 __thread

另见 Common variable attributes