2014-03-28 62 views
6

我正在面对一些奇怪的行为与thread_local,并不确定我是否做错了什么或它是一个GCC错误。 我有以下最低的摄制场景:thread_local成员变量构造

#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 

Ideone

有了它注释掉,就变成这样:

main 
foo 
foo 
42 
42 

Ideone

我只是在这里失去了一些愚蠢的东西?

$ 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) 

更新:

这提供了意想不到的效果还有:

#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 
+0

为了增加混乱,与酒吧更换b.Foo :: FOO按预期工作: [Ideone(http://ideone.com/QnIEXp) –

+1

注意,静态thread_local bar :: foo FREE_FOO;静态''没有效果,因为你只是修改那里的链接(默认为内部)。删除它,你会得到相同的行为。 – Andy

+0

间接访问的模板类成员也保持未初始化,而对非模板类成员的同一访问触发两种初始化: [Ideone](http://ideone.com/3mBIoO) –

回答

5

这是太长了评论,尽管我不我声称完全理解它。

我有一个较短的版本,你可以在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试图如果FOO决定,它是bar的一个静态成员,需要一个包装函数来检测它是否已被初始化...它决定不需要包装,这是不正确的。它检查

  1. 它不是error_operand_p吗?是的,事实并非如此。 (我猜)
  2. 它是thread_local(DECL_THREAD_LOCAL_P)?是的,它是thread_local。
  3. 它不是_gnread __thread扩展名(DECL_GNU_TLS_P)?是的,事实并非如此。
  4. 它没有在函数范围(DECL_FUNCTION_SCOPE_P)中声明吗?是的,事实并非如此。
  5. 该变量未在另一个翻译单元(TU)中定义吗?是的,事实并非如此。 (bug?)
  6. 它没有一个不平凡的析构函数吗?是的,它没有。
  7. 它有没有初始化或常量?它有一个初始化器,但它是不变的。
  8. 它并不需要一个包装

的缺陷或者是:

  1. 结论,如果初始化是常量,那么它是不是动态初始化或
  2. 未能正确做静态初始化或
  3. 未能注意到,即使它是一个成员变量,它也可以在外部定义

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

下面的代码

/* 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)); 
} 
+0

如果您可以将您的发现添加到bug [报告](http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60702),那就太好了。谢谢! –

+1

@AnomanderRake clang也没有通过你创建的测试以及我的简短版本,我将它提交给了clang团队,他们发现它是一个他们最近修复的bug的副本,这个bug与通过'这个'指针。我会更新你的错误报告,指向固定的叮咚错误......我只是注意到理查德·史密斯在这里留下了一个评论。 – amdn

+1

@AnomanderRake我已经更新了你的gcc bug报告,我添加了较短的测试用例,并指出gcc团队进行了叮铛声修复,我认为这比我的猜测更有效率。 – amdn

相关问题