【问题标题】:Static *template* class member across dynamic library跨动态库的静态 *template* 类成员
【发布时间】:2014-06-09 07:31:28
【问题描述】:

编辑:接受的答案下方的 cmets 表明它可能是 Android 动态加载程序的问题。

我有一个带有静态成员的模板类的标题。在运行时,静态成员的地址用于库和客户端代码中。模板在库和客户端代码中都被隐式实例化。它在 Linux 和 OSX 上运行良好,符号重复但标记为“唯一”,如 nm 所示(见下文)。 但是,当我为 ARM (Android) 编译时,该符号在 DSO 和可执行文件中都被标记为弱。加载器不统一,符号在运行时有效复制!

我读过这些: two instances of a static member, how could that be? Static template data members storage 尤其是这个答案: https://stackoverflow.com/a/2505528/2077394 和: http://gcc.gnu.org/wiki/Visibility

但我还是有点疑惑。我知道可见性的属性有助于优化,但我认为它应该默认工作。我知道 C++ 标准不关心共享库,但这是否意味着使用共享库会破坏标准? (或者至少这个实现不符合 C++ 标准?) 奖励:我该如何解决? (并且不使用模板是不可接受的答案:))

标题:

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

然后构建,查看符号定义:

产生.so:

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

编译和链接main:

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

符号被标记为“唯一”:

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

产生.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

编译和链接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

符号很弱!

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

编辑,注意上下文:我在玩 OOLua,一个帮助将 C++ 绑定到 Lua 的库,当我开始以 Android 为目标时,我的单元测试失败了。我不“拥有”代码,我宁愿对其进行深入修改。

编辑,在 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

【问题讨论】:

    标签: c++ templates c++11 android-ndk shared-libraries


    【解决方案1】:

    您可以调整一些编译器/链接器设置以启用此功能(您查看过-fvisibility 标志吗?)。

    可能值得尝试使用 GCC 属性修饰符(在变量上显式设置 __attribute__ ((visibility ("default"))))。

    如果做不到这一点,我可以建议的唯一解决方法是:(都有些难看):

    1. 显式实例化在共享库中创建的所有模板形式,并在其实现中提供初始化程序(而不是在标头中)。这可能会也可能不会。
    2. 与 (1) 类似,但使用 shim 函数作为共享变量的 myers 单例(示例如下)。
    3. 在映射中为基于 rtti 的类分配一个变量(这也可能在共享库边界上失败)。

    例如

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

    可执行文件还必须为其用于实例化模板的任何类型提供实现。

    【讨论】:

    • 是的,我知道可能的解决方法,如果我要设计自己的库,我会这样做。但在这里这是不可能的,因为我不“拥有”代码,我宁愿对它进行深入修改。我对 OOLUA 有这个问题。现在我想知道我的测试用例是否应该按照标准工作。如果它应该可以工作,那么我可以将其视为 GCC 错误!
    【解决方案2】:

    Android 不支持唯一符号。它是 ELF 格式的 GNU 扩展,仅适用于 GLIBC 2.11 及更高版本。 Android 根本不使用 GLIBC,它使用了一个不同的 C 运行时,称为 Bionic。

    (update) 如果弱符号对你不起作用(end update)恐怕你必须修改代码使其不依赖静态数据。

    【讨论】:

    • 谢谢。只要您开始使用共享库,我是否应该得出结论,Android 工具链不完全支持 C++ 标准?
    • “只要您开始使用共享库,Android 工具链就不完全支持 C++ 标准”——我自己对弱符号的实验(在 Linux 上)表明它们确实适用于跨平台的静态数据共享库。也就是说,链接器将强制只使用一个弱符号,其他实例将被忽略。你试过运行你的程序吗?
    • 是的,打印的地址不一样(仅限安卓)
    • 您是否使用dlopen 打开您的共享库?当地址确实不同时就是这种情况。
    • 地址应该是一样的,在Linux上也是一样的。这意味着模板与它无关。这纯粹是一个 Android libc 问题。
    猜你喜欢
    • 1970-01-01
    • 2021-01-21
    • 2011-03-05
    • 2012-04-13
    • 2018-10-05
    • 1970-01-01
    • 1970-01-01
    • 2020-07-22
    • 2010-11-04
    相关资源
    最近更新 更多