ラベル GCC の投稿を表示しています。 すべての投稿を表示
ラベル GCC の投稿を表示しています。 すべての投稿を表示

2013年12月17日火曜日

main() のすり替え in C++

[2013/12/18] ptr_umain 経由の呼び出しで引数を付けていなかった点を修正。

ある種のライブラリの場合、main() の呼び出し前に処理をしておきたい場合があります。ものすごく真っ当な方法で言うならスタートアップルーチンを書くべきところではありますが、その場合、ライブラリを使う側でもオプション指定が必要になったりしてちょっと面倒になります。こういう時に、ライブラリ側で main() を定義してしまいユーザーコード側では

// main.h
#define main _umain
#ifdef __cplusplus
extern "C" int _umain(int argc, char **argv);
#else
extern int _umain(); // No specification for parameters in C
#endif

// main.c
#include "main.h"
int main(int argc, char **argv) { /* ... */ } // replaced with _umain

のようにヘッダを読み込ませることによって main() をすり替えてしまい、ライブラリ側の main() からすり替えた _umain() を呼び出す、といったことが行われる場合があります。例えば SDL (http://www.libsdl.org/) なんかはこれを使っているようです。

が、問題が一つ。main() は引数が固定されていません。実用的には、以下の3種類くらいあると考えてよいでしょう。

int main(void);
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);

C の場合、これでもそんなに問題になりません。すり替え用マクロが有効であるとして、上記コードのように extern int _umain(); としておけば、C 言語では引数に関しては指定がない扱いであり、かつ可変引数関数を実現する C の呼び出し規約上、上記いずれの定義であっても _umain(argc, argc, envp); と呼び出してしまえば、普通の処理系ならまず問題なく動作するはずです。

が、C++ の場合、プロトタイプ宣言が必須であり、また、関数の型チェックが必ず行われます。つまり main() がどの形式で定義されていたかによって呼び分ける必要が出てくるわけです。前述の SDL では main() の型を int main(int, char**) に限定することで回避しているようです。最初から特定ライブラリを使う前提であればそれでもいいのですが、今作成中のライブラリ(UTF-8 Win32 API)は後付けの形での利用を想定しているので限定はちょっとつらいです。

要はユーザーがどの形式で main() を定義したか判別する方法があればいいわけです。そこで(処理系依存ですが) GCC の weak symbol と alias を使用してみました。

int _umain_stub()
{
 return 0;
}

// for C
extern "C" int _umain(...) __attribute__((weak, alias("_Z11_umain_stubv")));
// for C++
extern int _umain() __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv, char **envp) __attribute__((weak, alias("_Z11_umain_stubv")));

static int(*ptr_umain)(...)                   = static_cast<int(*)(...)>(_umain);
static int(*ptr_umain0)()                     = static_cast<int(*)()>(_umain);
static int(*ptr_umain1)(int)                  = static_cast<int(*)(int)>(_umain);
static int(*ptr_umain2)(int, char**)          = static_cast<int(*)(int, char**)>(_umain);
static int(*ptr_umain3)(int, char**, char**)  = static_cast<int(*)(int, char**, char**)>(_umain);

template<typename T>
static inline bool is_target(T t)
{
 return t != reinterpret_cast<T>(_umain_stub);
}

int main(int argc, char **argv, char **envp)
{
 if(is_target(ptr_umain))  return ptr_umain(argc, argv, envp);
 if(is_target(ptr_umain0)) return ptr_umain0();
 if(is_target(ptr_umain1)) return ptr_umain1(argc);
 if(is_target(ptr_umain2)) return ptr_umain2(argc, argv);
 if(is_target(ptr_umain3)) return ptr_umain3(argc, argv, envp);
 return -1; // Not reached if main() is user-defined
}

通常、同名の関数定義がある場合、多重定義エラーになります。weak symbol を使うと、他に同名の定義がない場合だけ有効になる定義を作ることができます。デフォルト実装の定義を作っておいて、場合によってはより高速な実装に差し替えることも可能、みたいな使い方が典型的なユースケースです。alias はある関数を別の関数の別名として定義できる機能です。上記コードでは C/C++ の _umain() 系を全て _umain_stub(void) の別名かつ weak symbol としています。ユーザーがある形式の main() を定義した場合、マクロで _umain() にすり替えられていずれかの weak symbol が上書きされます。結果として _umain_stub(void) と値(アドレス)が変わるため、それを is_target() で判別して呼び分けているわけです。

で、話が済んでいれば幸せだったのですが、なぜか StrawberryPerl 5.18.1.1 32bit 中の GCC を使って、↑ main() のあるソースファイルで #include <iostream> や #include <cstdio> すると関数ポインタの値がずれます。Cygwin の i686-w64-ming32-g++ だと問題ありません。両方とも GCC-4.7.3 なんですが。例えば実際のアドレスが、
__Z7_umain_v (ユーザー定義の _umain(void)) -> 0x401560
__Z11_umain_stubv (_umain_stub(void)) -> 0x40195e
である場合に、バイナリ中で参照されるアドレスがそれぞれ、0x40117e, 0x40157c だったりします。両方とも 0x3e2 だけずれています。バグなのか設定がおかしいのかちょっと分かりませんがとりあえず以下のようなコードで無理やり補正すると一応動作はします。ユーザー定義の _umain() と _umain_stub(void) の 2 種類があるだけで、かつ、ユーザー定義は 1 つだけ存在するはず、という前提で多数派が実際には _umain_stub(void) であるとみなしてその差を補正しています。

template<typename T>
static inline unsigned long get_offset(T t)
{
 return reinterpret_cast<unsigned long>(_umain_stub) - reinterpret_cast<unsigned long>(t);
}

template<typename T>
static inline void add_offset(T& t, unsigned long offset)
{
 t = reinterpret_cast<T>(reinterpret_cast<unsigned long>(t) + offset);
}

void fix_fptr()
{
 std::map<unsigned long, int> counter;
 ++counter[get_offset(ptr_umain)];
 ++counter[get_offset(ptr_umain0)];
 ++counter[get_offset(ptr_umain1)];
 ++counter[get_offset(ptr_umain2)];
 ++counter[get_offset(ptr_umain3)];
// assert(counter.size() == 2);
 unsigned long offset = counter.begin()->second > (++(counter.begin()))->second ? counter.begin()->first : (++(counter.begin()))->first;
 add_offset(ptr_umain, offset);
 add_offset(ptr_umain0, offset);
 add_offset(ptr_umain1, offset);
 add_offset(ptr_umain2, offset);
 add_offset(ptr_umain3, offset);
}

ちょっとどうだかなという感じではあるので、とりあえず、#include <iostream> や #include <cstdio> を外して様子見な感じですが謎な状態です。

2012年9月4日火曜日

Variadic Template にまつわる Workaround

C++11 は確かに便利、なのだが現状はまだ実装が十分と言えず回避手段(workaround)を取らざるを得ない場合がある。の割には余りそういう説明を見ない、ということでせっかく書いたのでメモってみる。まぁ Boost のソースコードの中にたくさん埋まっているのだろうし、ひょっとしたら C++er は息をするように workaround が書ける人種なのかもしれないが、そこはそれ。

sorry, unimplemented: use of ‘type_pack_expansion’ in template

#include <utility>
struct B { int operator()(int n) const { return n; } };

template<typename C>
struct A {

#if __GNUC__ == 4 && (__GNUC_MINOR__ <= 5 || __GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ == 0)

  template<typename ... Args>
  struct deduce {
      typedef decltype(C()(std::declval<Args&&>()...)) type;
  };
  template<typename ... Args>
  typename deduce<Args...>::type operator()(Args && ... args) const
  { return C()(std::forward<Args>(args)...); }

#else

  template<typename ... Args>
// sorry, unimplemented: use of ‘type_pack_expansion’ in template
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48292
  auto operator()(Args && ... args) const -> decltype(C()(std::forward<Args>(args)...))
  { return C()(std::forward<Args>(args)...); }

#endif
};

このエラーメッセージが出るのは恐らくこの場合には限らないと思うのだが、type pack (Args ... みたいな展開)を tailing return type 中で使った場合。GCC bugzilla にも書いてある通り、別のクラスに切り出すことで回避できる。@iorate さんの記事([C++][Boost] C++ で一般化された on を書く)にも workaround の方法を含め書かれている。後は、GCC 4.6.1 から直っている、という情報くらいだろうか。

sorry, unimplemented: cannot expand ‘Args ...’ into a fixed-length argument list

#include <boost/fusion/include/vector.hpp>

#include <boost/fusion/adapted/mpl.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include "variadic_bridge.hpp"

template<typename ... Args>
struct C {

#if __GNUC__ == 4 && __GNUC_MINOR__ <= 6

  typename boost::fusion::result_of::as_vector<
    typename yak::util::variadic_to_vector<Args...>::type
  >::type v;

#else

// sorry, unimplemented: cannot expand ‘Args ...’ into a fixed-length argument list
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653
  boost::fusion::vector<Args...> v;

#endif
};

こっちはエラーメッセージを日本語対象でググっても合っているものは tweet 1件しかヒットしない。……みんな困ってそうなものなのだが。variadic な template parameter を非 variadic な template に渡せない、というものだ。自作ヘルパ variadic_bridge.hpp を使って variadic な template parameter を MPL vector に変換して、さらに boost::fusion::vector に変換している。まぁ MPL vector まで持って来られれば後はどうとでもなるとは思うが、一応、固定長引数として Metafunction Class に渡す variadic_to_fixed も用意はしてある。こっちは GCC 4.7.0 から修正。

まとめ?

そもそも VC は 2012 でも variadic template 対応してねーよ、という時点で PP するしかないという話なのが悲しいところ。

しかし、CSS はブラウザバグとその対処、みたいなものが結構見つかるのだが C++ についてはそういうまとめみたいなものはないのだろうか。