thread_local成员变量构造 [英] thread_local member variable construction

查看:229
本文介绍了thread_local成员变量构造的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用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

Ideone



如果取消注释,成为:

  main 
foo
foo
42
42

Ideone



我只是在这里缺少一些愚蠢的东西?

  $ 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 是否需要封装函数以检测它是否已经被初始化...它决定不需要包装,这是不正确的。它检查


  1. 这不是一个error_operand_p吗?是的,它不是。 (我猜)

  2. 是否是thread_local(DECL_THREAD_LOCAL_P)?是的是thread_local。

  3. 它不是gnu __thread扩展(DECL_GNU_TLS_P)吗?是,不是。

  4. 它是否未在函数范围(DECL_FUNCTION_SCOPE_P)中声明?是,不是。

  5. 变量未在其他翻译单元(TU)中定义?是的,它不是。 (bug?)

  6. 它没有一个非平凡的析构函数吗?是,它不会。

  7. 它没有初始值设定项或常数项吗?

>缺陷是:


  1. 总结如果初始化器是常量,那么它不是动态初始化的,或

  2. 未能正确执行静态初始化,或

  3. 无法注意到即使它是成员变量,它也可以在外部定义

由于初始化是由构造函数完成的,我认为这是混乱的根源,一个构造函数被调用,但值是一个常量。



这里是代码

  / *如果我们可以知道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

Ideone

With it uncommented, it becomes this:

main
foo
foo
42
42

Ideone

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

  1. Is it not an error_operand_p ? Yes, it is not. (I guess)
  2. Is it thread_local (DECL_THREAD_LOCAL_P) ? Yes it is thread_local.
  3. Is it not gnu __thread extension (DECL_GNU_TLS_P) ? Yes, it is not.
  4. Is it not declared in function scope (DECL_FUNCTION_SCOPE_P) ? Yes, it is not.
  5. Is the variable not defined in another translation unit (TU)? Yes, it is not. (bug?)
  6. Does it not have a non-trivial destructor? Yes, it does not.
  7. Does it have no initializer or a constant one? It has an initializer, but it is constant.
  8. It doesn't need a wrapper

The flaw is either:

  1. Concluding that if the initializer is constant then it isn't dynamically initialized, or
  2. Failing to properly do the static initialization, or
  3. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆