ビルド時に Direct Access 警告が発生する場合の対処方法 - C++ プログラミング

PROGRAM


ビルド時に Direct Access 警告が発生する場合の対処方法

Xcode 4.6.2 を使って Objective-C++ コードをビルドしていたときに、次の警告が表示されることがありました。

ld: warning: direct access in -[**CLASS** **METHOD**] to global weak symbol **FUNCTION** means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings.

警告メッセージから察するに、Objective-C++ クラスの **CLASS** が持つインスタンスメソッド **METHOD** の中で呼び出している C++ の **FUNCTION** 関数がグローバルウィークシンボルで、これが実行時に適切に処理できない可能性があるようです。

どのような支障をきたすかは理解できていないのですけど、予期しない実装(シンボル)と取り違えてしまうかもしれないという感じでしょうか。

 

こんな警告がどこで発生しているのかを調べて行くと、今回の自分のケースでは、次の箇所が引き金になっていたことが判ってきました。

以下ではこれらそれぞれについて、警告を無くすための対処方法を見て行きたいと思います。

 

グローバルウィークシンボル警告を解消する

インライン関数を関数ポインタに代入していた場面

原因となりうる場面はいくつかあるのでしょうけど、ひとつは、グローバルウィークシンボルとして指摘された C++ 関数は、別の静的ライブラリに inline 指定で実装されていた静的メンバ関数でした。

その関数ポインタを Objective-C++ クラスのインスタンスメソッド内で使用して呼び出ししていたのが原因だった様子です。

 

具体的には、まず次のような inline 静的メンバ関数を、別の静的ライブラリのヘッダーファイルに実装していました。

class CppCLASS

{

static inline int FUNCTION(int arg)

{

return f(arg);

}

}

そしてこれを、別の静的ライブラリのセットとして用意して、それを Objective-C++ コードにリンクしました。

実装はヘッダーファイルに書いているので、別の静的ライブラリというところは重要ではなさそうですけど、ともあれそしてこのヘッダーファイルを Objective-C++ コードに #include して、インスタンスメソッド内でその関数のポインタを変数に格納しました。

@implementation ObjCppCLASS

{

std::function<bool(char)> _func;

}

 

- (void)method

{

_func = CppCLASS::FUNCTION;

 

}

このようにしていたところ、関数ポインタを使うかどうかに関わらず、ビルドのリンカの段階で direct access in *** to global weak symbol の警告が表示されてしまう様子でした。

 

この場合、次の二つのどちらかを採れば、グローバルウィークシンボル警告が発生しないようにできる様子でした。

このそれぞれについて、次のように実装を調整すれば良さそうでした。

 

インライン関数としない方法

その静的メンバ関数を実装したクラスを自分で実装しているなら、それをインライン関数にしないようにすることで対応できます。

インライン関数でないようにするには、まず、ヘッダーファイルで、非 inline なプロトタイプ宣言だけを記載します。

class CppCLASS

{

static int FUNCTION(int arg);

}

そして、その実装ファイル内で関数の内容を定義します。

int CppClass::FUNCTION(int arg)

{

return f(arg);

}

これで、この静的メンバ関数のポインタを扱っても、グローバルウィークシンボル警告が発生しないようになりました。

このとき inline キーワードを外しても、実装をヘッダーファイルに書いたままだとインライン関数として扱われてしまうので注意です。

 

自前の関数でラップする方法

インライン関数を普通の関数にできない場合は、自前の関数をひとつ作って、その中でそのインライン関数を呼び出すことでもグローバルウィークシンボル警告を回避できる様子でした。

自前の関数を作る方法としては、ここでは、ラムダ関数を使う方法と、ラッパ関数を使う方法の 2 つを紹介します。

1.ラムダ関数を使う方法

自前の関数を作る方法はいくつかありますけど、C++11 の ラムダ関数 に対応している処理系なら、それを使うのが簡単かもしれません。

@implementation ObjCppCLASS

 

- (void)method

{

_func = [](int arg) { return CppCLASS::FUNCTION(arg); });

 

}

ここで、ラムダ関数を代入している _func の型は std::function<int(int)> になります。

テンプレート引数の中の、最初の int が戻り値の型で、続く括弧の中が引数の型のリストです。

 

2.ラッパ関数を定義する方法

または、普通に関数を定義して、その中で目的の関数を呼び出す方法でも大丈夫です。

static int wrapFunction(int arg)

{

return CppCLASS::FUNCTION(arg);

}

 

@implementation ObjCppCLASS

 

- (void)method

{

_func = wrapFunction;

 

}

このように目的のインライン関数に対してラッパ関数をひとつ定義して、このラッパ関数のポインタを扱うことで direct access in *** to global weak symbol 警告が発生しないようになりました。

 

std::function で関数ポインタを管理する場面

今回は、同じような警告が、もうひとつ別の理由でも発生しました。

ld: warning: direct access  in std::__1::function::operator()(char) const  to global weak symbol std::__1::bad_function_call::~bad_function_call()

警告メッセージはこのように、インライン関数のポインタを変数に代入したときと同じです。

ただ、今回のこの警告は、発生した場所が "std::__1::function::operator()(char) const" だったり、呼び出そうとしたウィークシンボルが "std::__1::bad_function_call::~bad_function_call()" だったりと、自分が実装したところとは少しずれていそうです。

 

調べてみるとどうやら、Objective-C++ クラスのインスタンス変数内で管理していた std::function<bool(char)> 型の変数を、同クラスのインスタンスメソッド内から () 演算子で呼び出そうとしたころで発生した様子です。

つまり、次のような実装になっていました。

@implementation ObjCppCLASS

{

std::function<bool(char)> _func;

}

 

- (void)execute

{

std::cout << _func('a') << std::endl;

}

このとき、"_func('a')" を実行しようとしたところで、先ほどの警告が発生しました。

今度の場合は _func に代入されている関数が問題なのではなくて、どうやらそこで使っている std::function<> の () 演算子の実装と何か関係がある様子です。

 

ちなみにここで垣間見れる std::bad_function_call というのは、std::function<> に適切な関数が割り当てられていないまま () 演算子で実行しようとしたときに発生する例外です。

関数の割り当て状況に限らずこの警告が表示されるようだったので、きっと std::function<> の実装の問題なのかもしれません。

 

回避策としては、std::function<> ではなく、従前からの C 関数ポインタ を使って関数ポインタを管理するようにします。

つまり、管理する関数ポインタの変数を "std::function<bool(char)> _func" ではなく、次のように置き換えます。

bool (*_func)(char);

関数ポインタの変数を書き慣れていないと判りにくいですが、これで _func という変数が定義できています。

この定義に変更しても、それまでの "std::function<bool(char)>" と同じように使えます。「char を受け取って bool を返す関数」のポインタを代入することができますし、"[](char)->bool { }" といった ラムダ関数 も代入することができます。

 

呼び出しも () を使って行えるので、宣言の部分だけ修正すれば、他の部分は手を加えなくても大丈夫そうです。

ただし、このように C 関数ポインタで管理するようにしても、代入の時には、インライン関数をそのまま代入しようとするとグローバルウィークシンボル警告が発生するので、そちらはそちらで適切に対処しないといけません。

 

ところで、ウィークシンボルとは

ちなみにウィークシンボルというのは、複数のライブラリをリンクするときに、重複する実装が含まれていても正しくリンクできるようにするための仕組みのようです。

同じ名前の関数が複数のライブラリで実装されていたとき、ウィークシンボルとして登録されている方は無視して、ウィークシンボルではない方をその関数の実装として、リンカが採用することになるそうです。

クラス宣言内の関数はウィークシンボルとして扱われる

そして、クラス宣言の中で書いたメンバ関数は、ウィークシンボルとして扱われるらしいです。

このような仕組みになっている理由は、クラス定義はヘッダーファイルに記載され、さまざまな場所(オブジェクトファイルや別のライブラリなど)でインクルードされて使われるため、ウィークシンボルになっていないと、実装が重複して、どれを採用したらいいか収拾がつかなくなってしまうからのようです。

通常は、実装ファイルで定義した関数が非ウィークシンボルとして、唯一どこかのライブラリに生成され、全てのウィークシンボルはその実装を使うことになります。

 

冒頭の警告は、このウィークシンボルの解決のされ方次第では、関数ポインタが指す実行内容と、本来のシンボルが行う実行内容とが違うものになる可能性があることを言っているのかもしれません。

インライン関数のケースで考えられること

ここで、インライン関数はヘッダーファイルで定義する必要があります。

その実装は、基本的にこの関数が呼び出された箇所に個々に埋め込まれる形になりますが、状況によってはそうならない場合もあるそうです。いずれにしても、定義上はウィークシンボルとして存在しているのではないかと思います。

もしかすると、今回の関数ポインタとして渡すときのように、インライン展開しようのないところでは、それがインライン展開された関数が生成されて、それを指すポインタが得られるのかもしれません。

いずれにせよ、インライン関数がウィークシンボルとして扱われ、それを関数ポインタとして指定されたために、今回のような警告が発生したと捉えられそうです。


[ もどる ]