簡體   English   中英

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

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

這是我在Mac OS X上使用clang ++面臨的問題的縮小版本。這是經過嚴格編輯以更好地反映真正的問題(第一次嘗試描述問題並沒有表現出問題)。

失敗

我在C ++中有一大塊軟件,在目標文件中有大量符號,所以我使用-fvisibility=hidden來保持我的符號表很小。 眾所周知,在這種情況下,必須特別注意vtable,我想我遇到了這個問題。 然而,我不知道如何以一種令gcc和clang取悅的方式優雅地解決它。

考慮一個base類,它具有一個包含一些有效負載的向下轉換運算符asderived類模板。 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);

然后我有兩個不同的編譯單元提供類似的服務,我可以將其近似為相同特征的兩倍:從base向下轉換為derive<payload>

// bar.cc
#include "foo.hh"
void bar(const base& b)
{
  b.as<derived<payload>>();
}

// baz.cc
#include "foo.hh"
void baz(const base& b)
{
  b.as<derived<payload>>();
}

從這兩個文件中,我構建了一個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;
  }

最后,一個Makefile來編譯和鏈接每個人。 這里沒什么特別的,當然,除了-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

在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

但是對於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

但是,如果我使用它可行

struct API payload {};

但我不想暴露有效載荷類型。 所以我的問題是:

  1. 為什么GCC和Clang在這里有所不同?
  2. 它是真的與GCC合作,還是我在使用未定義的行為時只是“幸運”?
  3. 我是否有辦法避免使用Clang ++公開payload

提前致謝。

使用不可見類型參數鍵入可見類模板的相等性(EDIT)

我現在對正在發生的事情有了更好的了解。 看起來GCC clang都要求類模板及其參數可見(在ELF意義上)以構建唯一類型。 如果更改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>>();
}

如果您也使payload可見:

struct API payload {};

然后你會看到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

類型相等很容易檢查,實際上只有一個類型的實例化,如其唯一地址所見。

但是,如果從payload刪除visible屬性:

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 type:  0x10faf1090
bar equal: 1
baz value: 0x10faea120
baz type:  0x10fafb090
baz equal: 1

現在有幾個derived<payload>類型derived<payload>實例化(由三個不同的地址見證),但是GCC認為這些類型是相同的,並且(當然)兩個dynamic_cast傳遞。

在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

還有三種類型的實例化(刪除失敗的dynamic_cast確實顯示有三種),但這一次,它們不相等,而dynamic_cast (當然)失敗。

現在的問題變成:1。作者想要的兩個編譯器之間的差異2.如果不是,兩者之間的“預期”行為是什么

我更喜歡GCC的語義,因為它允許真正實現類型擦除而無需公開公開包裝類型。

我已經向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目前的行為更為安全。 太糟糕了。

我最近遇到了這個問題,而@akim(OP)已經診斷出來了。

解決方法是編寫自己的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) );
}

請注意,如果兩個模塊具有不相關的不同Foo類型,則可能存在誤報。 模塊應將其私有類型放在匿名名稱空間中以避免這種情況。

我不知道如何類似地處理中間類型,因為我們只能檢查typeid的確切類型,並且不能迭代類型繼承樹。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM