为什么只有其中一些 C++ 模板实例导出到共享库中? [英] Why are only some of these C++ template instantiations exported in a shared library?

查看:140
本文介绍了为什么只有其中一些 C++ 模板实例导出到共享库中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 C++ 动态库(在 macOS 上),它有一个模板化函数,其中包含一些在公共 API 中导出的显式实例化.客户端代码只看到模板声明;他们不知道里面发生了什么,并依赖这些实例在链接时可用.

出于某种原因,这些显式实例中只有一些在动态库中可见.

这是一个简单的例子:

//libtest.cpp#define VISIBLE __attribute__((visibility("default")))模板T foobar(T arg) {返回参数;}模板 int VISIBLE foobar(int);模板 int* VISIBLE foobar(int*);

我希望两个实例都可见,但只有非指针实例是:

$ clang++ -dynamiclib -O2 -Wall -Wextra -std=c++1z -stdlib=libc++ -fvisibility=hidden -fPIC libtest.cpp -o libtest.dylib$ nm -gU libtest.dylib |C++过滤器0000000000000f90 T int foobar(int)

这个测试程序链接失败,因为缺少指针1:

//client.cpp模板T foobar(T);//假设这是在库头文件中int main() {foob​​ar(1);foob​​ar(nullptr);返回0;}

$ clang++ -O2 -Wall -Wextra -std=c++1z -stdlib=libc++ -L.-ltest 客户端.cpp -o 客户端体系结构 x86_64 的未定义符号:int* foobar(int*)",引用自:_main 在 client-e4fe7d.old:找不到架构 x86_64 的符号clang:错误:链接器命令失败,退出代码为 1(使用 -v 查看调用)

类型和可见性之间似乎确实存在某种联系.如果我将返回类型更改为 void,它们都是可见的(即使模板参数仍然是指针或其他什么).特别奇怪的是,这两个都导出:

template auto VISIBLE foobar(int) ->国际;模板自动可见 foobar(int*) ->整数*;

这是一个错误吗?为什么明显的语法糖会改变行为?

如果我将模板定义更改为可见,它会起作用,但它似乎不理想,因为只有少数这些实例应该被导出......而且我仍然想了解为什么会发生这种情况,无论哪种方式.

我使用的是 Apple LLVM 版本 8.0.0 (clang-800.0.42.1).

解决方案

您的问题可在 linux 上重现:

$ clang++ --versionclang 版本 3.8.0-2ubuntu4 (tags/RELEASE_380/final)目标:x86_64-pc-linux-gnu线程模型:posix$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden \-fPIC libtest.cpp -o libtest.so$ nm -C libtest.so |grep foobar0000000000000620 W int foobar(int)0000000000000630 t int* foobar(int*)

非指针重载是弱全局的,但指针重载是本地.

clang 对 __attribute__ 的松弛诊断掩盖了这种情况的原因语法扩展,这毕竟是 GCC 的发明.如果我们编译g++ 而我们得到:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.solibtest.cpp:9:36: 警告:在非类类型上忽略可见性"属性 [-Wattributes]模板 int * 可见 foobar(int *);^

注意 g++ 仅在指针重载时忽略可见性属性,并且,就像 clang - 与该警告一致 - 它发出代码:

$ nm -C libtest.so |grep foobar0000000000000610 W int foobar(int)0000000000000620 t int* foobar(int*)

很明显,clang 也在做同样的事情,但没有告诉我们为什么.

满足g++的重载与1和另一个不满意的是intint *的区别.在此基础上,我们希望 g++ 对更改感到满意:

template int VISIBLE foobar(int);//模板 int * VISIBLE foobar(int *);模板浮动可见 foobar(float);

原来是这样:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so$ nm -C libtest.so |grep foobar0000000000000650 W float foobar(float)0000000000000640 W int foobar(int)

叮当声也是:

$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so$ nm -C libtest.so |grep foobar0000000000000660 W float foobar(float)0000000000000650 W int foobar(int)

对于 T 非指针类型的重载,它们都可以执行您想要的操作,但是不与 T 一个指针类型.

然而,你在这里面对的是不是动态可见函数的禁令返回指针而不是非指针.它不可能逃脱注意,如果visibility 就这样被破坏了.这只是对表单类型的禁令:

D __attribute__((visibility("...")))

其中 D 是指针或引用类型,与以下形式的类型不同:

E __attribute__((visibility("..."))) *

或:

E __attribute__((visibility("..."))) &

其中E 不是指针或引用类型.区别在于:

  • A(具有可见性的指针或引用...)输入 D

和:

  • 具有可见性的(类型的指针或引用 E)...

见:

$ cat demo.cpp整数 xx ;int __attribute__((visibility("default"))) * pvxx;//行int * __attribute__((visibility("default"))) vpxx;//不好int __attribute__((visibility("default"))) &rvxx = xx;//行,国际与__attribute__((visibility("default"))) vrxx = xx;//不好$ g++ -shared -Wall -Wextra -std=c++1z -fvisibility=hidden -o libdemo.so demo.cppdemo.cpp:3:46: 警告:在非类类型上忽略可见性"属性 [-Wattributes]int * __attribute__((visibility("default"))) vpxx;//不好^demo.cpp:5:46: 警告:在非类类型上忽略可见性"属性 [-Wattributes]国际与__attribute__((visibility("default"))) vrxx = xx;//不好^$ nm -C libdemo.so |grep xx0000000000201030 B pvxx0000000000000620 R rvxx0000000000201038 b vpxx0000000000000628 r vrxx0000000000201028 b xx

OK 声明成为全局符号;不好的变成本地的,并且只有前者是动态可见的:

nm -CD libdemo.so |grep xx0000000000201030 B pvxx0000000000000620 R rvxx

这种行为是合理的.我们不能指望编译器有属性指针或引用的全局、动态可见性,可以指向或指的是没有全局或动态可见性的东西.

这种合理的行为只会让您的目标受挫,因为- 正如您现在可能看到的:

template int VISIBLE foobar(int);模板 int* VISIBLE foobar(int*);

并不意味着你认为它做了什么.你认为,对于给定的类型 U,

template U VISIBLE foobar(U);

声明一个具有 default 的模板实例化函数可见性,接受 U 类型的参数并返回相同的参数.实际上,它声明了一个模板实例化函数,它接受一个参数输入 U 并返回类型:

U __attribute__((visibility("default")))

允许用于 U = int,但不允许用于 U = int *.

为了表达您的意图,template 的实例化T foobar(T arg)应为动态可见函数,限定模板函数的类型本身具有可见性属性.根据 GCC 的 __attribute__ 文档语法 - 这无可否认没有具体说明模板 - 您必须创建一个属性函数在声明中的限定而不是它的定义.所以遵守有了这个,你会修改你的代码,如:

//libtest.cpp#define VISIBLE __attribute__((visibility("default")))模板T foobar(T arg) 可见;模板T foobar(T arg) {返回参数;}模板 int foobar(int);模板 int* foobar(int*);

g++ 不再有任何抱怨:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so$ nm -CD libtest.so |grep foobar0000000000000640 W int foobar(int)0000000000000650 W int* foobar(int*)

并且两个重载都是动态可见的.叮当也是一样:

$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so$ nm -CD libtest.so |grep foobar0000000000000650 W int foobar(int)0000000000000660 W int* foobar(int*)

运气好的话,您将在 Mac OS 上使用 clang 获得相同的结果

I have a C++ dynamic library (on macOS) that has a templated function with some explicit instantiations that are exported in the public API. Client code only sees the template declaration; they have no idea what goes on inside it and are relying on these instantiations to be available at link time.

For some reason, only some of these explicit instantiations are made visible in the dynamic library.

Here is a simple example:

// libtest.cpp
#define VISIBLE __attribute__((visibility("default")))

template<typename T> T foobar(T arg) {
    return arg;
}

template int  VISIBLE foobar(int);
template int* VISIBLE foobar(int*);

I would expect both instantiations to be visible, but only the non-pointer one is:

$ clang++ -dynamiclib -O2 -Wall -Wextra -std=c++1z -stdlib=libc++ -fvisibility=hidden -fPIC libtest.cpp -o libtest.dylib
$ nm -gU libtest.dylib | c++filt
0000000000000f90 T int foobar<int>(int)

This test program fails to link because the pointer one is missing:

// client.cpp
template<typename T> T foobar(T);  // assume this was in the library header

int main() {
    foobar<int>(1);
    foobar<int*>(nullptr);
    return 0;
}

$ clang++ -O2 -Wall -Wextra -std=c++1z -stdlib=libc++ -L. -ltest client.cpp -o client
Undefined symbols for architecture x86_64:
  "int* foobar<int*>(int*)", referenced from:
      _main in client-e4fe7d.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

There does seem to be some connection between the types and the visibility. If I change the return type to void, they are all visible (even if the template arguments are still pointers or whatever). Especially bizarre, this exports both:

template auto VISIBLE foobar(int)  -> int;
template auto VISIBLE foobar(int*) -> int*;

Is this a bug? Why would apparent syntactic sugar change behavior?

It works if I change the template definition to be visible, but it seems non-ideal because only a few of these instantiations should be exported... and I still want to understand why this is happening, either way.

I am using Apple LLVM version 8.0.0 (clang-800.0.42.1).

解决方案

Your problem is reproducible on linux:

$ clang++ --version
clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
Target: x86_64-pc-linux-gnu
Thread model: posix

$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden \
-fPIC libtest.cpp -o libtest.so

$ nm -C libtest.so | grep foobar
0000000000000620 W int foobar<int>(int)
0000000000000630 t int* foobar<int*>(int*)

The non-pointer overload is weakly global but the pointer overload is local.

The cause of this is obscured by clang's slack diagnosing of the __attribute__ syntax extension, which after all is a GCC invention. If we compile with g++ instead we get:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so
libtest.cpp:9:36: warning: ‘visibility’ attribute ignored on non-class types [-Wattributes]
 template int * VISIBLE foobar(int *);
                                    ^

Notice that g++ ignores the visibility attribute only in the pointer overload, and, just like clang - and consistent with that warning - it emits code with:

$ nm -C libtest.so | grep foobar
0000000000000610 W int foobar<int>(int)
0000000000000620 t int* foobar<int*>(int*)

Clearly clang is doing the same thing, but not telling us why.

The difference between the overloads that satisfies g++ with one and dissatisfies it with the other is the difference between int and int *. On that basis we'd expect g++ to be satisfied with the change:

template int  VISIBLE foobar(int);
//template int * VISIBLE foobar(int *);
template float VISIBLE foobar(float);

And so it is:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so
$ nm -C libtest.so | grep foobar
0000000000000650 W float foobar<float>(float)
0000000000000640 W int foobar<int>(int)

And so is clang:

$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so
$ nm -C libtest.so | grep foobar
0000000000000660 W float foobar<float>(float)
0000000000000650 W int foobar<int>(int)

Both of them will do what you want for overloads with T a non-pointer type, but not with T a pointer type.

What you face here, however, is not a ban on dynamically visible functions that return pointers rather than non-pointers. It couldn't have escaped notice if visibility was as broken as that. It is just a ban on types of the form:

D __attribute__((visibility("...")))

where D is a pointer or reference type, as distinct from types of the form:

E __attribute__((visibility("..."))) *

or:

E __attribute__((visibility("..."))) &

where E is not a pointer or reference type. The distinction is between:

  • A (pointer or reference that has visibility ...) to type D

and:

  • A (pointer or reference to type E) that has visibility ...

See:

$ cat demo.cpp 
int  xx ;
int __attribute__((visibility("default"))) * pvxx; // OK
int * __attribute__((visibility("default"))) vpxx; // Not OK
int __attribute__((visibility("default"))) & rvxx = xx; // OK,
int & __attribute__((visibility("default"))) vrxx = xx; // Not OK


$ g++ -shared -Wall -Wextra -std=c++1z -fvisibility=hidden -o libdemo.so demo.cpp 
demo.cpp:3:46: warning: ‘visibility’ attribute ignored on non-class types [-Wattributes]
 int * __attribute__((visibility("default"))) vpxx; // Not OK
                                              ^
demo.cpp:5:46: warning: ‘visibility’ attribute ignored on non-class types [-Wattributes]
 int & __attribute__((visibility("default"))) vrxx = xx; // Not OK
                                          ^

$ nm -C libdemo.so | grep xx
0000000000201030 B pvxx
0000000000000620 R rvxx
0000000000201038 b vpxx
0000000000000628 r vrxx
0000000000201028 b xx

The OK declarations become global symbols; the Not OK ones become local, and only the former are dynamically visible:

nm -CD libdemo.so | grep xx
0000000000201030 B pvxx
0000000000000620 R rvxx

This behaviour is reasonable. We can't expect a compiler to attribute global, dynamic visibility to a pointer or reference that could point or refer to something that does not have global or dynamic visibility.

This reasonable behaviour only appears to frustrate your objective because - as you probably now see:

template int  VISIBLE foobar(int);
template int* VISIBLE foobar(int*);

doesn't mean what you thought it did. You thought that, for given type U,

template U VISIBLE foobar(U);

declares a template instantiating function that has default visibility, accepting an argument of type U and returning the same. In fact, it declares a template instantiating function that accepts an argument of type U and returns type:

U __attribute__((visibility("default")))

which is allowed for U = int, but disallowed for U = int *.

To express your intention that instantations of template<typename T> T foobar(T arg) shall be dynamically visible functions, qualify the type of the template function itself with the visibility attribute. Per GCC's documentation of the __attribute__ syntax - which admittedly says nothing specific concerning templates - you must make an attribute qualification of a function in a declaration other than its definition. So complying with that, you'd revise your code like:

// libtest.cpp
#define VISIBLE __attribute__((visibility("default")))

template<typename T> T foobar(T arg) VISIBLE;

template<typename T> T foobar(T arg) {
    return arg;
}

template int  foobar(int);
template int* foobar(int*);

g++ no longer has any gripes:

$ g++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so
$ nm -CD libtest.so | grep foobar
0000000000000640 W int foobar<int>(int)
0000000000000650 W int* foobar<int*>(int*)

and both of the overloads are dynamically visible. The same goes for clang:

$ clang++ -shared -O2 -Wall -Wextra -std=c++1z -fvisibility=hidden -fPIC libtest.cpp -o libtest.so
$ nm -CD libtest.so | grep foobar
0000000000000650 W int foobar<int>(int)
0000000000000660 W int* foobar<int*>(int*)

With any luck, you'll have the same result with clang on Mac OS

这篇关于为什么只有其中一些 C++ 模板实例导出到共享库中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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