简体   繁体   English

使用clang ++,-fvisibility = hidden,typeinfo和type-erasure

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

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). 这是我在Mac OS X上使用clang ++面临的问题的缩小版本。这是经过严格编辑以更好地反映真正的问题(第一次尝试描述问题并没有表现出问题)。

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. 我在C ++中有一大块软件,在目标文件中有大量符号,所以我使用-fvisibility=hidden来保持我的符号表很小。 It is well known that in such a case one must pay extra attention to the vtables, and I suppose I face this problem. 众所周知,在这种情况下,必须特别注意vtable,我想我遇到了这个问题。 I don't know however, how to address it elegantly in a way that pleases both gcc and clang. 然而,我不知道如何以一种令gcc和clang取悦的方式优雅地解决它。

Consider a base class which features a down-casting operator, as , and a derived class template, that contains some payload. 考虑一个base类,它具有一个包含一些有效负载的向下转换运算符asderived类模板。 The pair base / derived<T> is used to implement type-erasure: pair base / derived<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);
  }
};

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> : 然后我有两个不同的编译单元提供类似的服务,我可以将其近似为相同特征的两倍:从base向下转换为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. 从这两个文件中,我构建了一个dylib。 Here is the main function, calling these functions from the dylib: 这是main函数,从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. 最后,一个Makefile来编译和链接每个人。 Nothing special here, except, of course, -fvisibility=hidden . 这里没什么特别的,当然,除了-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: 在OS X上运行成功与gcc(4.8):

$ 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: 但是对于clang(3.4),这会失败:

$ 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? 为什么GCC和Clang在这里有所不同?
  2. is it really working with GCC, or I was just "lucky" in my use of undefined behavior? 它是真的与GCC合作,还是我在使用未定义的行为时只是“幸运”?
  3. do I have a means to avoid making payload go public with Clang++? 我是否有办法避免使用Clang ++公开payload

Thanks in advance. 提前致谢。

Type equality of visible class templates with invisible type parameters (EDIT) 使用不可见类型参数键入可见类模板的相等性(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. 看起来GCC clang都要求类模板及其参数可见(在ELF意义上)以构建唯一类型。 If you change the bar.cc and baz.cc functions as follows: 如果更改bar.ccbaz.cc函数,如下所示:

// 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: 如果您也使payload可见:

struct API payload {};

then you will see that both GCC and Clang will succeed: 然后你会看到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 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 : 但是,如果从payload删除visible属性:

struct payload {};

then you get with GCC: 然后你得到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. 现在有几个derived<payload>类型derived<payload>实例化(由三个不同的地址见证),但是GCC认为这些类型是相同的,并且(当然)两个dynamic_cast传递。

In the case of clang, it's different: 在clang的情况下,它是不同的:

$ 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. 还有三种类型的实例化(删除失败的dynamic_cast确实显示有三种),但这一次,它们不相等,而dynamic_cast (当然)失败。

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 现在的问题变成:1。作者想要的两个编译器之间的差异2.如果不是,两者之间的“预期”行为是什么

I prefer GCC's semantics, as it allows to really implement type-erasure without any need to expose publicly the wrapped types. 我更喜欢GCC的语义,因为它允许真正实现类型擦除而无需公开公开包装类型。

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: 我已经向LLVM的人们报告了这个问题,并且首先注意到如果它适用于GCC,那是因为:

I think the difference is actually in the c++ library. 我认为差异实际上在c ++库中。 It looks like libstdc++ changed to always use strcmp of the typeinfo names: 看起来libstdc ++改为始终使用typeinfo名称的strcmp:

https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964 https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=149964

Should we do the same with libc++? 我们应该对libc ++做同样的事吗?

To this, it was clearly answered that : 对此, 明确回答

No. It pessimizes correctly behaving code to work around code that violates the ELF ABI. 不会。它会严重正确地运行代码以解决违反ELF ABI的代码。 Consider an application that loads plugins with RTLD_LOCAL. 考虑使用RTLD_LOCAL加载插件的应用程序。 Two plugins implement a (hidden) type called "Plugin". 两个插件实现了一个名为“插件”的(隐藏)类型。 The GCC change now makes this completely separate types identical for all RTTI purposes. 现在,GCC的变化使得这个完全独立的类型对于所有RTTI目的都是相同的。 That makes no sense at all. 这毫无意义。

So I can't do what I want with Clang: reduce the number of published symbols. 所以我不能用Clang做我想做的事:减少已发布符号的数量。 But it appears to be saner than the current behavior of GCC. 但它似乎比GCC目前的行为更为安全。 Too bad. 太糟糕了。

I've run into this problem recently, and @akim (the OP) has diagnosed it. 我最近遇到了这个问题,而@akim(OP)已经诊断出来了。

A workaround is to write your own dynamic_cast_to_private_exact_type<T> or somesuch that checks the typeid 's string name. 解决方法是编写自己的dynamic_cast_to_private_exact_type<T>或其他检查typeid的字符串名称的方法。

template<class T>
struct dynamic_cast_to_exact_type_helper;
template<class T>
struct dynamic_cast_to_exact_type_helper<T*>
{
  template<class U>
  T* operator()(U* u) const {
    if (!u) return nullptr;
    auto const& uid = typeid(*u);
    auto const& tid = typeid(T);
    if (uid == tid) return static_cast<T*>(u); // shortcut
    if (uid.hash_code() != tid.hash_code()) return nullptr; // hash compare to reject faster
    if (uid.name() == tid.name()) return static_cast<T*>(u); // compare names
    return nullptr;
  }
};
template<class T>
struct dynamic_cast_to_exact_type_helper<T&>
{
  template<class U>
  T& operator()(U& u) const {
    T* r = dynamic_cast_to_exact_type<T&>{}(std::addressof(u));
    if (!r) throw std::bad_cast{};
    return *r;
  }
}
template<class T, class U>
T dynamic_cast_to_exact_type( U&& u ) {
  return dynamic_cast_to_exact_type_helper<T>{}( std::forward<U>(u) );
}

Note that this can have false positives, if two modules have a different Foo type that is unrelated. 请注意,如果两个模块具有不相关的不同Foo类型,则可能存在误报。 Modules should put their private types in anonymous namespaces to avoid this. 模块应将其私有类型放在匿名名称空间中以避免这种情况。

I don't know how to similarly handle intermediate types, as we can only check the exact type in a typeid comparsion and cannot iterate over the type inheritance tree. 我不知道如何类似地处理中间类型,因为我们只能检查typeid的确切类型,并且不能迭代类型继承树。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM