I'm reading Kerrisk's book and see that the following as a note on 31-4,
- The
__thread
keyword must immediately follow thestatic
orextern
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.
I wonder the reason of the fact that the keyword must come behind static
or extern
. Can't it be used without any of them?
Its example code,
/*************************************************************************\
* 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] = '\0'; /* 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 standard C storage class specifier for thread local variables is _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.
Thus, the standard says that storage class keywords (static
, extern
, auto
— don't use it! — register
— ditto — _Thread_local
and typedef
) should appear at the start of a declaration. Where either static
or extern
appears and _Thread_local
, the recommendation from the book is that static
or extern
should be first and _Thread_local
second.
Of course, the book is using __thread
and not _Thread_local
. That's a compiler (implementation) specific keyword that behaves similarly to the standard C _Thread_local
and Microsoft's __declspec(thread)
.
The GCC documentation on thread local storage documents (emphasis added):
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 theextern
orstatic
specifiers, but with no other storage class specifier. When used withextern
orstatic
,__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.
So, what you're seeing is the GCC-specific notation for thread-local storage, and as I noted and as the GCC manual notes, storage class information should come first in a declaration (and GCC explicitly says __thread
after static
or extern
).
See also Common variable attributes.