2014-03-28 12 views
6

Ich bin mit thread_local mit einem merkwürdigen Verhalten konfrontiert und bin mir nicht sicher, ob ich etwas falsch mache oder es sich um einen GCC-Fehler handelt. Ich habe folgende minimale Repro Szenario:thread_local Elementvariable Konstruktion

#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; 
} 

Mit der kommentierten Zeile über der Ausgabe sieht wie folgt aus:

main 
0 

Ideone

Mit ihm unkommentiert, wird es dies:

main 
foo 
foo 
42 
42 

Ideone

Fehle ich hier gerade etwas dummes?

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

Dies auch unerwartete Ergebnisse liefert:

#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; 
} 

Ergebnis:

main 
0 
far::foo 
bar::foo 
42 
+0

zu Verwirrung hinzuzufügen, zu ersetzen b.Foo mit Bar :: FOO wie erwartet funktioniert: [Ideone] (http://ideone.com/QnIEXp) –

+1

Beachten Sie, dass die 'static' auf' static thread_local bar :: foo FREE_FOO; 'hat keine Wirkung, da Sie nur die Verknüpfung dort ändern (die standardmäßig auf internal). Entfernen Sie es und Sie erhalten das gleiche Verhalten. – Andy

+0

Ein Member einer Template-Klasse, auf die indirekt zugegriffen wird, bleibt ebenfalls nicht initialisiert, während derselbe Zugriff auf das Member einer Nicht-Template-Klasse beide Initialisierungen auslöst: [Ideone] (http://ideone.com/3mBIoO) –

Antwort

5

Dies ist zu lang für einen Kommentar, obwohl ich don‘ t behaupten, es vollständig zu verstehen.

Ich habe eine kürzere Version, die Sie 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 
} 

laufen kann ich denke, der Fehler in dieser gcc-Quelldatei ist

https://chromium.googlesource.com/native_client/nacl-gcc/+/upstream/master/gcc/cp/decl2.c

An diesem Punkt gcc versucht, wenn FOO zu entscheiden, , das ein statisches Mitglied von bar ist, benötigt eine Wrapper-Funktion, um festzustellen, ob es initialisiert wurde ... es entscheidet, dass kein Wrapper benötigt wird, was inkorrekt ist. Es prüft

  1. Ist es nicht ein error_operand_p? Ja, es ist nicht. (Ich denke)
  2. Ist es thread_local (DECL_THREAD_LOCAL_P)? Ja, es ist thread_local.
  3. Ist es nicht gnu __thread Erweiterung (DECL_GNU_TLS_P)? Ja, es ist nicht.
  4. Ist es nicht im Funktionsumfang deklariert (DECL_FUNCTION_SCOPE_P)? Ja, es ist nicht.
  5. Ist die Variable nicht in einer anderen Übersetzungseinheit (TU) definiert? Ja, es ist nicht. (Bug?)
  6. Hat es nicht einen nicht-trivialen Destruktor? Ja, tut es nicht.
  7. Hat es keinen Initialisierer oder einen konstanten? Es hat einen Initialisierer, aber es ist konstant.
  8. Es braucht nicht einen Wrapper

Der Fehler ist entweder:

  1. Schluss, dass, wenn die initializer konstant ist, dann wird es nicht dynamisch initialisiert oder
  2. Failing richtig tun, um die statische Initialisierung, oder
  3. Nicht zu bemerken, dass, obwohl es eine Member-Variable ist, könnte es extern definiert werden

Da die Initialisierung vom Konstruktor durchgeführt wird, denke ich, dass das die Quelle der Verwirrung ist, ein Konstruktor wird aufgerufen, aber der Wert ist eine Konstante.

Hier ist der 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)); 
} 
+0

Es wäre toll, wenn Sie Ihre Ergebnisse zum Bug hinzufügen könnten [Bericht] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60702). Vielen Dank! –

+1

@AnomanderRake clang hatte auch den Test, den du erstellt hast, sowie meine kürzere Version nicht bestanden. Ich legte das dem Claning Team vor und sie fanden heraus, dass es ein Duplikat eines kürzlich behobenen Bugs war, der mit dem Zugriff auf thread_local statisch über ' dieser Zeiger. Ich werde deinen Bugreport aktualisieren, um auf den Fixed-Clang-Bug zu zeigen ... Ich merke gerade, dass Richard Smith hier einen Kommentar zu diesem Effekt hinterlassen hat. – amdn

+1

@AnomanderRake Ich habe Ihren GCC-Bug-Bericht aktualisiert, ich habe den kürzeren Testfall hinzugefügt und das GCC-Team auf den Clang-Fix hingewiesen, ich denke, das ist produktiver als meine Spekulationen. – amdn

Verwandte Themen