使用clang ++,-fvisibility = hidden,typeinfo和type-erasure [英] Using clang++, -fvisibility=hidden, and typeinfo, and type-erasure
问题描述
这是我在Mac OS X上使用clang ++时遇到的问题的缩小版本。为了更好地反映真正的问题(第一次尝试描述问题未展示问题),对此进行了严格编辑。
失败
我在C ++中有这么大一部分软件,在目标文件中有大量符号,所以我使用 -fvisibility = hidden
来保持我的符号表小。众所周知,在这种情况下,人们必须特别注意vtables,我想我会面临这个问题。然而,我不知道如何以一种令gcc和clang好的方式来优雅地解决它。
考虑一个 base
类,它具有向下转换运算符,作为
和一个派生
类模板,其中包含一些有效载荷。 base
/ 衍生的< T>
用于实现类型删除:
// foo.hh
#define API __attribute __((visibility(default)))
struct API base
{
virtual〜base(){}
template< typename T>
const T& as()const
{
return dynamic_cast< const T&>(* this);
}
};
模板< typename T>
派生的API API:base
{};
struct payload {}; // *不*标记为默认可见性。
API void bar(const base& b);
API void baz(const base& b);
然后我有两个不同的编译单元提供类似的服务,我可以近似两倍功能:从 base
下降到派生<有效载荷>
:
// bar.cc
#includefoo.hh
void bar(const base& b)
{
b.as<衍生LT有效载荷GT;>();
}
和
<$ p
$ b b.as<衍生LT有效载荷GT;>();
}
从这两个文件中,我构建了一个dylib。这里是 main
函数,从dylib调用这些函数:
// main.cc
#include< stdexcept>
#include< iostream>
#includefoo.hh
int main()
try
{
derived< payload> d;
bar(d);
baz(d);
}
catch(std :: exception& e)
{
std :: cerr<< e.what()<<的std :: ENDL;
}
最后,使用Makefile来编译并链接每个人。这里没什么特别的,当然除了 -fvisibility = hidden
。
CXX = clang ++
CXXFLAGS = -std = c ++ 11 -fvisibility =隐藏
全部:主要
主要:main.o bar.dylib baz。 dylib
$(CXX)-o $ @ $ ^
%.dylib:%.cc foo.hh
$(CXX)$(CXXFLAGS)-shared -o $ @ $<
%.o:%.cc foo.hh
$(CXX)$(CXXFLAGS)-c -o $ @ $<
clean:
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
使用gcc(4.8)在OS X上运行成功:
$ make clean&& make CXX = g ++ - mp-4.8&& ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden - c main.cc -o main.o
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
g ++ - mp-4.8 -o main main.o bar.dylib baz.dylib
然而,如果使用clang(3.4),则会失败:
$ make clean&&使CXX =铿锵++ - mp-3.4&& ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden - c main.cc -o main.o
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
铿锵声++ - mp-3.4 -o main main.o bar.dylib baz.dylib
std :: bad_cast
然而,如果我使用
struct API payload {};
但我不想公开有效内容类型。所以我的问题是:
- 为什么GCC和Clang在这里有所不同?
- 是真的与GCC合作,或者我在使用未定义的行为时只是幸运?
- 我是否有办法避免使
有效载荷
使用Clang ++公开发布?
预先致谢。
使用不可见类型参数(EDIT)可见类模板的类型相等
现在我已经更好地理解发生了什么。看起来,GCC 和 clang都需要类模板及其参数都可见(在ELF意义上)以构建唯一类型。如果您更改 bar.cc
和 baz.cc
函数,如下所示:
// bar.cc
#includefoo.hh
void bar(const base& b)
{
std :: cerr
<< bar值:<< & typeid(b)<< std :: endl
<< 酒吧类型:<< & typeid(派生<有效载荷>)<< std :: endl
<< 酒吧平等:<< (typeid(b)== typeid(派生<有效载荷>))<<的std :: ENDL;
b.as<派生<有效载荷>>();
和如果有效载荷
也可见:
结构API有效载荷{};
然后你会看到GCC和Clang都会成功:
$ make clean&&& make CXX = g ++ - mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -c -o main.o main.cc
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
./g++-mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar价值:0x106785140
酒吧类型:0x106785140
酒吧等于:1
baz价值:0x106785140
baz类型:0x106785140
baz等于:1
$ make clean&& make CXX = clang ++ - mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -c -o main.o main.cc
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
铿锵声++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
铿锵声++ - mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar值:0x10a6d5110
bar类型:0x10a6d5110
bar等于:1
baz值:0x10a6d5110
baz类型:0x10a6d5110
baz等于: 1
类型相等很容易检查,实际上有一个类型的实例,如目击它的唯一地址。
然而,如果您从有效内容
中删除可见属性:
struct payload {};
然后您使用GCC:
$ make clean&& make CXX = g ++ - mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -c -o main.o main.cc
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
g ++ - mp-4.8 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
g ++ - mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar value:0x10faea120
bar类型:0x10faf1090
bar等于:1
baz值:0x10faea120
baz类型:0x10fafb090
baz等于: 1
现在有几个实例类型派生< payload> code>(由三个不同的地址见证),但GCC认为这些类型是平等的,并且(当然)两个
dynamic_cast
通过。
在clang的情况下,它是不同的:
$ make clean&& amp ; make CXX = clang ++ - mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -c -o main.o main.cc
clang ++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o bar.dylib bar.cc
铿锵声++ - mp-3.4 -std = c ++ 11 -fvisibility = hidden -shared -o baz.dylib baz.cc
.clang ++ - mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar value:0x1012ae0f0
bar type:0x1012b3090
bar equal:0
std :: bad_cast
这个类型还有三个实例(删除失败的 dynamic_cast
确实显示有三个),但这一次,它们并不相同,并且 dynamic_cast
(当然)失败。
现在问题变成:
1.他们作者希望的编译器之间的差异是
2.如果不是,两者之间的预期行为是什么?
更喜欢GCC的语义,因为它允许真正实现type-e rasure,而不需要公开包装的类型。
我已经向LLVM的人员报告过这个问题,如果它在GCC的情况下起作用,那么首先要注意的是,这是因为:
我认为这种差异实际上是在c ++库中。它看起来像
libstdc ++更改为始终使用typeinfo名称的strcmp:
https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964
我们应该如何处理libc ++?
为此,它是明确回答说::
没有。它正确地使用正确的行为代码来解决
违反ELF ABI的代码。考虑使用
RTLD_LOCAL加载插件的应用程序。两个插件实现了一个名为插件的(隐藏)类型。
GCC的变化现在使得这种完全分开的类型对于所有
RTTI目的是相同的。这完全没有意义。
所以我不能用Clang来做我想要的:减少已发布符号的数量。但它似乎比GCC目前的行为更为清晰。太糟糕了。
This is a scaled down version of a problem I am facing with clang++ on Mac OS X. This was seriously edited to better reflect the genuine problem (the first attempt to describe the issue was not exhibiting the problem).
The failure
I have this big piece of software in C++ with a large set of symbols in the object files, so I'm using -fvisibility=hidden
to keep my symbol tables small. It is well known that in such a case one must pay extra attention to the vtables, and I suppose I face this problem. I don't know however, how to address it elegantly in a way that pleases both gcc and clang.
Consider a base
class which features a down-casting operator, as
, and a derived
class template, that contains some payload. The pair base
/derived<T>
is used to implement type-erasure:
// foo.hh
#define API __attribute__((visibility("default")))
struct API base
{
virtual ~base() {}
template <typename T>
const T& as() const
{
return dynamic_cast<const T&>(*this);
}
};
template <typename T>
struct API derived: base
{};
struct payload {}; // *not* flagged as "default visibility".
API void bar(const base& b);
API void baz(const base& b);
Then I have two different compilation units that provide a similar service, which I can approximate as twice the same feature: down-casting from base
to derive<payload>
:
// bar.cc
#include "foo.hh"
void bar(const base& b)
{
b.as<derived<payload>>();
}
and
// baz.cc
#include "foo.hh"
void baz(const base& b)
{
b.as<derived<payload>>();
}
From these two files, I build a dylib. Here is the main
function, calling these functions from the dylib:
// main.cc
#include <stdexcept>
#include <iostream>
#include "foo.hh"
int main()
try
{
derived<payload> d;
bar(d);
baz(d);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
Finally, a Makefile to compile and link everybody. Nothing special here, except, of course, -fvisibility=hidden
.
CXX = clang++
CXXFLAGS = -std=c++11 -fvisibility=hidden
all: main
main: main.o bar.dylib baz.dylib
$(CXX) -o $@ $^
%.dylib: %.cc foo.hh
$(CXX) $(CXXFLAGS) -shared -o $@ $<
%.o: %.cc foo.hh
$(CXX) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
The run succeeds with gcc (4.8) on OS X:
$ make clean && make CXX=g++-mp-4.8 && ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c main.cc -o main.o
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
g++-mp-4.8 -o main main.o bar.dylib baz.dylib
However with clang (3.4), this fails:
$ make clean && make CXX=clang++-mp-3.4 && ./main
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c main.cc -o main.o
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
std::bad_cast
However it works if I use
struct API payload {};
but I do not want to expose the payload type. So my questions are:
- why are GCC and Clang different here?
- is it really working with GCC, or I was just "lucky" in my use of undefined behavior?
- do I have a means to avoid making
payload
go public with Clang++?
Thanks in advance.
Type equality of visible class templates with invisible type parameters (EDIT)
I have now a better understanding of what is happening. It is appears that both GCC and clang require both the class template and its parameter to be visible (in the ELF sense) to build a unique type. If you change the bar.cc
and baz.cc
functions as follows:
// bar.cc
#include "foo.hh"
void bar(const base& b)
{
std::cerr
<< "bar value: " << &typeid(b) << std::endl
<< "bar type: " << &typeid(derived<payload>) << std::endl
<< "bar equal: " << (typeid(b) == typeid(derived<payload>)) << std::endl;
b.as<derived<payload>>();
}
and if you make payload
visible too:
struct API payload {};
then you will see that both GCC and Clang will succeed:
$ make clean && make CXX=g++-mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
./g++-mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x106785140
bar type: 0x106785140
bar equal: 1
baz value: 0x106785140
baz type: 0x106785140
baz equal: 1
$ make clean && make CXX=clang++-mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x10a6d5110
bar type: 0x10a6d5110
bar equal: 1
baz value: 0x10a6d5110
baz type: 0x10a6d5110
baz equal: 1
Type equality is easy to check, there is actually a single instantiation of the type, as witnessed by its unique address.
However, if you remove the visible attribute from payload
:
struct payload {};
then you get with GCC:
$ make clean && make CXX=g++-mp-4.8
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
g++-mp-4.8 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
g++-mp-4.8 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
g++-mp-4.8 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x10faea120
bar type: 0x10faf1090
bar equal: 1
baz value: 0x10faea120
baz type: 0x10fafb090
baz equal: 1
Now there are several instantiation of the type derived<payload>
(as witnessed by the three different addresses), but GCC sees these types are equal, and (of course) the two dynamic_cast
pass.
In the case of clang, it's different:
$ make clean && make CXX=clang++-mp-3.4
rm -f main main.o bar.o baz.o bar.dylib baz.dylib libba.dylib
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -c -o main.o main.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o bar.dylib bar.cc
clang++-mp-3.4 -std=c++11 -fvisibility=hidden -shared -o baz.dylib baz.cc
.clang++-mp-3.4 -o main main.o bar.dylib baz.dylib
$ ./main
bar value: 0x1012ae0f0
bar type: 0x1012b3090
bar equal: 0
std::bad_cast
There are also three instantiations of the type (removing the failing dynamic_cast
does show that there are three), but this time, they are not equal, and the dynamic_cast
(of course) fails.
Now the question turns into: 1. is this difference between both compilers wanted by their authors 2. if not, what is "expected" behavior between both
I prefer GCC's semantics, as it allows to really implement type-erasure without any need to expose publicly the wrapped types.
I had reported this to the people from LLVM, and it was first noted that if it works in the case of GCC, it's because:
I think the difference is actually in the c++ library. It looks like libstdc++ changed to always use strcmp of the typeinfo names:
https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964
Should we do the same with libc++?
To this, it was clearly answered that:
No. It pessimizes correctly behaving code to work around code that violates the ELF ABI. Consider an application that loads plugins with RTLD_LOCAL. Two plugins implement a (hidden) type called "Plugin". The GCC change now makes this completely separate types identical for all RTTI purposes. That makes no sense at all.
So I can't do what I want with Clang: reduce the number of published symbols. But it appears to be saner than the current behavior of GCC. Too bad.
这篇关于使用clang ++,-fvisibility = hidden,typeinfo和type-erasure的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!