使用clang ++,-fvisibility = hidden,typeinfo和type-erasure [英] Using clang++, -fvisibility=hidden, and typeinfo, and type-erasure

查看:536
本文介绍了使用clang ++,-fvisibility = hidden,typeinfo和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 {}; 

但我不想公开有效内容类型。所以我的问题是:


  1. 为什么GCC和Clang在这里有所不同?

  2. 真的与GCC合作,或者我在使用未定义的行为时只是幸运?

  3. 我是否有办法避免使有效载荷使用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:

  1. why are GCC and Clang different here?
  2. is it really working with GCC, or I was just "lucky" in my use of undefined behavior?
  3. 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屋!

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