thread_local成员变量构造 [英] thread_local member variable construction
问题描述
我使用thread_local面临一些奇怪的行为,不知道我是做错了什么,还是一个GCC错误。
我有以下最小的重复场景:
#include< iostream>
using namespace std;
struct bar {
struct foo {
foo(){
cerr<< foo< endl
}
int i = 42;
};
static thread_local foo FOO;
};
static thread_local bar :: foo FREE_FOO;
thread_local bar :: foo bar :: FOO;
int main(){
bar b;
cerr<< main< endl
// cerr<< FREE_FOO.i< endl
cerr<< b.FOO.i < endl
return 0;
}
上面的注释行如下所示:
main
0
$ b b
如果取消注释,成为:
main
foo
foo
42
42
我只是在这里缺少一些愚蠢的东西?
$ gcc -v
使用内置specs。
COLLECT_GCC = gcc
COLLECT_LTO_WRAPPER = / usr / lib / gcc / x86_64-linux-gnu / 4.8 / lto-wrapper
目标:x86_64-linux-gnu
配置为:。 ./src/configure -v --with-pkgversion ='Ubuntu / Linaro 4.8.1-10ubuntu9'--with-bugurl = file:///usr/share/doc/gcc-4.8/README.Bugs --enable -languages = c,c ++,java,go,d,fortran,objc,obj-c ++ --prefix = / usr --program- suffix = -4.8 --enable-shared --enable-linker-build- libexecdir = / usr / lib --unhout-included-gettext --enable-threads = posix --with-gxx-include-dir = / usr / include / c ++ / 4.8 --libdir = / usr / lib --enable- nls --with-sysroot = / --enable-clocale = gnu --enable-libstdcxx-debug --enable-libstdcxx-time = yes --enable-gnu-unique-object --enable-plugin --with-system -zlib --disable-browser-plugin --enable-java-awt = gtk --enable-gtk-cairo --with-java-home = / usr / lib / jvm / java-1.5.0 -gcj- amd64 / jre --enable-java-home --with-jvm-root-dir = / usr / lib / jvm / java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir = / usr /lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory = amd64 --with-ecj-jar = / usr / share / java / eclipse-ecj.jar --enable -objc-gc --enable-multiarch --disable-werror --with-arch-32 = i686 --with-abi = m64 --with-multilib-list = m32,m64,mx32 --with-tune = generic --enable-checking = release --build = x86_64-linux-gnu --host = x86_64-linux-gnu --target = x86_64-linux-gnu
线程模型:posix
gcc version 4.8。 1(Ubuntu / Linaro 4.8.1-10ubuntu9)
更新: / p>
这也提供了意想不到的结果:
#include< iostream> ;
using namespace std;
template< class T>
struct bar {
struct foo {
foo(){
cerr<< bar :: foo< endl
}
int i = 42;
};
void baz(){
cerr<< bar :: FOO.i < endl
}
static thread_local foo FOO;
};
struct far {
struct foo {
foo(){
cerr<< far :: foo< endl
}
int i = 42;
};
void baz(){
cerr<< far :: FOO.i < endl
}
static thread_local foo FOO;
};
template< class T> thread_local typename bar< T> :: foo bar< T> :: FOO;
thread_local typename far :: foo far :: FOO;
int main(){
cerr< main< endl
bar< int> b;
b.baz();
far f;
f.baz();
return 0;
}
结果:
main
0
far :: foo
bar :: foo
42
这个评论太长了,虽然我不是完全理解的。
我有一个较短的版本,您可以在 Coliru
#include< iostream>
using namespace std;
struct foo {
int i;
foo():i {42} {}
};
struct bar {
static thread_local foo FOO;
};
thread_local foo bar :: FOO;
int main(){
// cerr<< string((bar :: FOO.i == 42)?Ok:Bug)< endl // Ok
cerr<< string((bar()。FOO.i == 42)?Ok:Bug)< endl // Bug
}
我认为错误是在这个gcc源文件
https://chromium.googlesource.com/native_client/nacl-gcc/+/upstream/master/gcc/cp/decl2.c
此时gcc正在尝试决定 bar
的静态成员 FOO
是否需要封装函数以检测它是否已经被初始化...它决定不需要包装,这是不正确的。它检查
- 这不是一个error_operand_p吗?是的,它不是。 (我猜)
- 是否是thread_local(DECL_THREAD_LOCAL_P)?是的是thread_local。
- 它不是gnu __thread扩展(DECL_GNU_TLS_P)吗?是,不是。
- 它是否未在函数范围(DECL_FUNCTION_SCOPE_P)中声明?是,不是。
- 变量未在其他翻译单元(TU)中定义?是的,它不是。 (bug?)
- 它没有一个非平凡的析构函数吗?是,它不会。
- 它没有初始值设定项或常数项吗?
>缺陷是:
- 总结如果初始化器是常量,那么它不是动态初始化的,或
- 未能正确执行静态初始化,或
- 无法注意到即使它是成员变量,它也可以在外部定义
由于初始化是由构造函数完成的,我认为这是混乱的根源,一个构造函数被调用,但值是一个常量。
这里是代码
/ *如果我们可以知道VAR没有一个动态
初始值。 * /
static bool
var_defined_without_dynamic_init(tree var)
{
/ *如果在另一个TU中定义,我们不能告诉。 * /
if(DECL_EXTERNAL(var))
return false;
/ *如果它有一个非平凡的析构函数,注册析构函数
计数为动态初始化。 * /
if(TYPE_HAS_NONTRIVIAL_DESTRUCTOR(TREE_TYPE(var)))
return false;
/ *如果它在这个TU,它的初始化程序已经处理。 * /
gcc_assert(DECL_INITIALIZED_P(var));
/ *如果没有初始化器或常量,它不是动态的。 * /
return(!DECL_NONTRIVIALLY_INITIALIZED_P(var)
|| DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P(var));
}
/ *返回true iff VAR是一个需要使用
的变量,用于可能的动态初始化。 * /
static bool
var_needs_tls_wrapper(tree var)
{
return(!error_operand_p(var)
&& DECL_THREAD_LOCAL_P b $ b&&!DECL_GNU_TLS_P(var)
&&!DECL_FUNCTION_SCOPE_P(var)
&&!var_defined_without_dynamic_init(var));
}
I'm facing some strange behavior with thread_local and not sure whether I'm doing something wrong or it's a GCC bug. I have the following minimal repro scenario:
#include <iostream>
using namespace std;
struct bar {
struct foo {
foo () {
cerr << "foo" << endl;
}
int i = 42;
};
static thread_local foo FOO;
};
static thread_local bar::foo FREE_FOO;
thread_local bar::foo bar::FOO;
int main() {
bar b;
cerr << "main" << endl;
// cerr << FREE_FOO.i << endl;
cerr << b.FOO.i << endl;
return 0;
}
With the commented line above the output looks like this:
main
0
With it uncommented, it becomes this:
main
foo
foo
42
42
Am I just missing something stupid here?
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.1-10ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9)
Update:
This provides unexpected results as well:
#include <iostream>
using namespace std;
template<class T>
struct bar {
struct foo {
foo () {
cerr << "bar::foo" << endl;
}
int i = 42;
};
void baz() {
cerr << bar::FOO.i << endl;
}
static thread_local foo FOO;
};
struct far {
struct foo {
foo () {
cerr << "far::foo" << endl;
}
int i = 42;
};
void baz() {
cerr << far::FOO.i << endl;
}
static thread_local foo FOO;
};
template<class T> thread_local typename bar<T>::foo bar<T>::FOO;
thread_local typename far::foo far::FOO;
int main() {
cerr << "main" << endl;
bar<int> b;
b.baz();
far f;
f.baz();
return 0;
}
Result:
main
0
far::foo
bar::foo
42
This is too long for a comment, though I don't claim to fully understand it.
I have a shorter version you can run in Coliru
#include <iostream>
using namespace std;
struct foo {
int i;
foo() : i{42} {}
};
struct bar {
static thread_local foo FOO;
};
thread_local foo bar::FOO;
int main() {
//cerr << string((bar::FOO.i == 42) ? "Ok" : "Bug") << endl; //Ok
cerr << string((bar().FOO.i == 42) ? "Ok" : "Bug") << endl; //Bug
}
I think the bug is in this gcc source file
https://chromium.googlesource.com/native_client/nacl-gcc/+/upstream/master/gcc/cp/decl2.c
At this point gcc is trying to decide if FOO
, which is a static member of bar
, needs a wrapper function to detect if it has been initialized... it decides no wrapper is needed, which is incorrect. It checks
- Is it not an error_operand_p ? Yes, it is not. (I guess)
- Is it thread_local (DECL_THREAD_LOCAL_P) ? Yes it is thread_local.
- Is it not gnu __thread extension (DECL_GNU_TLS_P) ? Yes, it is not.
- Is it not declared in function scope (DECL_FUNCTION_SCOPE_P) ? Yes, it is not.
- Is the variable not defined in another translation unit (TU)? Yes, it is not. (bug?)
- Does it not have a non-trivial destructor? Yes, it does not.
- Does it have no initializer or a constant one? It has an initializer, but it is constant.
- It doesn't need a wrapper
The flaw is either:
- Concluding that if the initializer is constant then it isn't dynamically initialized, or
- Failing to properly do the static initialization, or
- Failing to notice that even though it is a member variable it could be externally defined
Since the initialization is done by the constructor, I think that is the source of the confusion, a constructor is called, but the value is a constant.
Here's the code
/* Returns true iff we can tell that VAR does not have a dynamic
initializer. */
static bool
var_defined_without_dynamic_init (tree var)
{
/* If it's defined in another TU, we can't tell. */
if (DECL_EXTERNAL (var))
return false;
/* If it has a non-trivial destructor, registering the destructor
counts as dynamic initialization. */
if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (var)))
return false;
/* If it's in this TU, its initializer has been processed. */
gcc_assert (DECL_INITIALIZED_P (var));
/* If it has no initializer or a constant one, it's not dynamic. */
return (!DECL_NONTRIVIALLY_INITIALIZED_P (var)
|| DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (var));
}
/* Returns true iff VAR is a variable that needs uses to be
wrapped for possible dynamic initialization. */
static bool
var_needs_tls_wrapper (tree var)
{
return (!error_operand_p (var)
&& DECL_THREAD_LOCAL_P (var)
&& !DECL_GNU_TLS_P (var)
&& !DECL_FUNCTION_SCOPE_P (var)
&& !var_defined_without_dynamic_init (var));
}
这篇关于thread_local成员变量构造的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!