Question

Edit: the comments below the accepted answer show that it might be an issue with the Android dynamic loader.

I have a header for a template class with a static member. At runtime the address of the static member is used in the library and in the client code. The template is implicitly instantiated both in the library and in the client code. It works fine on Linux and OSX, the symbol is duplicated but marked as "uniqued" as shown by nm (see below). However when I compile for ARM (Android), the symbol is marked weak in both the DSO and the executable. The loader does not unify and the symbol is effectively duplicated at runtime!

I read these: two instances of a static member, how could that be? Static template data members storage and especially this answer: https://stackoverflow.com/a/2505528/2077394 and: http://gcc.gnu.org/wiki/Visibility

but I am still a little bit puzzled. I understand that the attributes for visibility helps to optimize, but I thought it should work by default. I know the C++ standard does not care about shared library, but does it means that using shared libraries breaks the standard? (or at least this implementation is not C++ standard conform?) Bonus: how can I fix it? (and not using template is not an acceptable answer:))

Header:

template<class T>
struct TemplatedClassWithStatic {
    static int value;
};
template<class T>
int TemplatedClassWithStatic<T>::value = 0;

shared.cpp:

#include "TemplateWithStatic.hpp"
int *addressFromShared() {
    return &TemplatedClassWithStatic<int>::value;   
}

main.cpp:

#include "TemplateWithStatic.hpp"
#include <cstdio>

int *addressFromShared();
int main() {
    printf("%p %p\n", addressFromShared(),  &TemplatedClassWithStatic<int>::value);
}

And building, looking at the symbols definitions:

producing .so:

 g++-4.8  -shared src/shared.cpp  -o libshared.so -I include/ -fPIC

compiling and linking main:

 g++-4.8 src/main.cpp -I include/ -lshared -L.

symbols are marked as "unique":

nm -C -A *.so a.out | grep 'TemplatedClassWithStatic<int>::value'
libshared.so:0000000000200a70 u TemplatedClassWithStatic<int>::value
a.out:00000000006012b0 u TemplatedClassWithStatic<int>::value

producing .so

~/project/android-ndk-r9/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-g++    -o libshared.so src/shared.cpp   -I include/  --sysroot=/Users/amini/project/android-ndk-r9/platforms/android-14/arch-arm/ -shared

compiling and linking main

~/project/android-ndk-r9/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-g++  src/main.cpp  libshared.so    -I include/  --sysroot=${HOME}/project/android-ndk-r9/platforms/android-14/arch-arm/  -I ~/project/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.8/include -I ~/project/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/include -I ~/project/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.8/include/backward    -I ~/project/android-ndk-r9/platforms/android-14/arch-arm/usr/include  ~/project/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/libgnustl_static.a -lgcc

symbols are weak!

nm -C -A *.so a.out | grep 'TemplatedClassWithStatic<int>::value'
libshared.so:00002004 V TemplatedClassWithStatic<int>::value
a.out:00068000 V TemplatedClassWithStatic<int>::value

Edit, note for the context: I was playing with OOLua, a library helping binding C++ to Lua and my unittests were failing when I started to target Android. I don't "own" the code and I would rather modifying it deeply.

Edit, to run it on Android:

adb push libshared.so data/local/tmp/
adb push a.out data/local/tmp/ 
adb shell "cd data/local/tmp/ ; LD_LIBRARY_PATH=./ ./a.out"
0xb6fd7004 0xb004
Was it helpful?

Solution

Android does not support unique symbols. It is a GNU extension of ELF format that only works with GLIBC 2.11 and above. Android does not use GLIBC at all, it employs a different C runtime called Bionic.

(update) If weak symbols don't work for you (end update) I'm afraid you would have to modify the code such that it does not rely on static data.

OTHER TIPS

There may be some compiler/linker settings that you can tweak to enable this (have you looked at the -fvisibility flag?).

Possibly a GCC attribute modifier may be worth trying (explicitly set __attribute__ ((visibility ("default"))) on the variable).

Failing that, the only workarounds I could suggest are: (all are somewhat ugly):

  1. Explicitly instantiate all forms of the template that are created in the shared library and provide the initializers in its implementation (not in the header). This may or may not work.
  2. Like (1) but use a shim function as a myers singleton for the shared variable (example below).
  3. Allocate a variable in a map for the class based upon rtti (which might also fail across a shared library boundary).

e.g.

template<class T>
struct TemplatedClassWithStatic {
    static int& getValue() { return TemplatedClassWithStatic_getValue((T const*)0); }
};
// types used by the shared library.. can be forward declarations here but you run the risk of violating ODR.
int& TemplatedClassWithStatic_getValue(TypeA*);
int& TemplatedClassWithStatic_getValue(TypeB*);
int& TemplatedClassWithStatic_getValue(TypeC*);

shared.cpp

int& TemplatedClassWithStatic_getValue(TypeA*) {
   static int v = 0;
   return v;
}
int& TemplatedClassWithStatic_getValue(TypeB*) {
   static int v = 0;
   return v;
}
int& TemplatedClassWithStatic_getValue(TypeC*) {
   static int v = 0;
   return v;
}

The executable would also have to provide implementations for any types that it uses to instantiate the template.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top