11

最近、Haskell FFI を C/C++ に使用しているときに、C++ インライン関数で問題が発生しました。つまり、g++ は宣言された関数を実際にはインライン化せずinline、それらのシンボルを生成します。最終的に、インライン関数を呼び出すオブジェクト ファイルを ghci が読み込もうとすると、リンカー エラーが発生します。

Loading object (static) solveeq.o ... done
Loading object (dynamic) /usr/lib/gcc/x86_64-linux-gnu/4.6/libstdc++.so ... done
final link ... ghc: solveeq.o: unknown symbol `_ZN5Eigen8internal19throw_std_bad_allocEv'

ここで_ZN5Eigen8internal19throw_std_bad_allocEvinline、ヘッダーのみの Eigen C++ ライブラリの関数が何らかの形で実際の関数として扱われ、リンカー シンボルが与えられています。solveeq.oその関数への(間接的な)呼び出しを行う私のオブジェクトファイルです。環境はUbuntu 12.04 64bit、ghc 7.4.1です。

問題は次のとおりですextern "C" 。自分の関数の関数名の C++ 装飾を防ぐために使用できます。しかし、他の人が定義した C++ ヘッダーを変更することはできません/すべきではありません (明らかな理由により)。私の意見では、コンパイラは、このエラーを引き起こす最初の場所で、このインライン定義の関数を作成するべきではありません。理由は簡単です。問題の関数が本当にインライン化されている場合、リンカー エラーは発生しません。コンパイラが賢くなり、実際の関数を作成することを決定した場合、このようなエラーが発生します (または、他の場所で読んだように、同じ関数の複数の定義)。したがって、コンパイル/リンクの正確さはコンパイラの気分に依存します。

また、このようなリンカーの問題は、ヘッダーのみの C++ ライブラリ (移植性が魅力的) を事実上打ち負かすと思いますextern "C"

これは c++ の設計上の問題ですか、それとも単なる g++ の問題ですか? 私の質問は、c++ コンパイラまたは g++ がインライン関数をインライン化しないようにする方法はありますか? たとえば、このためのコマンド ライン オプションはありますか? (ライブラリコードなのでソースコードの改変は論外)

また、C++ STL がこの問題にどのように対処しているかにも興味があります。それらもヘッダーのみですか?

4

4 に答える 4

3

関数が存在することinlineは、コンパイラが適切と判断した場合はいつでも喜んで無視するコンパイラへのヒントです。gccのように、宣言さinlineれているがインラインではない関数について警告するコンパイラもありますが、すべてのインライン関数を無効にすると、実行できなくなります。テンプレートとして定義されたデータ構造を扱うかなり大きな関数がいくつかあります。暗黙的に含まれるすべてのテンプレート コードはインラインである可能性があり、これらのテンプレートのインスタンス化は複数の翻訳単位で発行される可能性があります。-Winline

C++ を他のツールと統合する目的で C++ の動作を押し付けようとする代わりに、他のツールはスマートになり、関心のないシンボルを無視する必要があります。特に、inline関数の不要なインスタンス化に本当に関心がない場合は、これらは無視してください。それらは複数の翻訳単位に現れる可能性があるため、通常は弱い記号を使用します。nm -po your-object-file.oUNIX システムでを使用し、grepツールが不快感を覚えるシンボルについては、通常のシンボル ( ) とは異なるものを使用してタグ付けされているはずですT

別のアプローチは、実際に公開したいシンボルのみを公開する共有オブジェクトをリンクし、このオブジェクトをツールで使用することです。とはいえ、このアプローチが実行可能であることはわかっていますが、実際に自分で使用したことはありません。

于 2014-12-25T03:31:46.040 に答える
2

別の回答がすでに指摘しているように、コンパイラが関数またはメソッドをインライン化しないことを決定した場合、コンパイラはそれを生成し、オブジェクト コード ファイルに入れます。これはエラーにはなりません。おそらく、コンパイラが特定の関数をインライン化しないことを決定したという事実は、ここでは問題ではありません。

以前、リンカから表示されたエラーを見たことがありますが、これは非常に誤解を招くエラー メッセージです。これは、テンプレート定義を含むヘッダー ファイルをインクルードするのを忘れていることを示しています。あなたが持っているとしましょう:

widget_decl_fwd.H:

template<typename C> void widget(C c_arg);

その後:

widget_decl_impl.H:

template<typename C> void widget(C c_arg)
{
     // Whatever widget() needs to do, here
}

次に、あなたは無邪気に進んで次のように書きます。

widget_factory.C:

#include "widget_decl_fwd.H"

void make_widgets(int n)
{
    widget(n);
}

次に、これをコンパイルしようとします。正常にコンパイルされますが、リンクに失敗します。コンパイラは、テンプレート関数の宣言を認識しましたが、その定義を認識しませんでした。そのため、コンパイラは widget() への呼び出しを外部シンボル参照にしました。一緒にリンクされた他のモジュールが宣言に引っ張られ、テンプレート関数をインスタンス化し、正しいシンボルをエクスポートしたことがたまたま発生した場合、リンクは成功する可能性がありますが、未解決のシンボルのために失敗する可能性があります。

このコンテキストでは、インライン関数はテンプレートに似ています。そのため、リンカーは未解決のシンボルについて不平を言っていますsolveeq.C

つまり、何solveeq.Cが含まれているかを確認し、このインライン関数またはテンプレートの宣言だけでなく、実際の定義がどこかから実際に取り込まれていることを確認する必要があります。ヘッダーのインクルードがわかりにくい場合は、-E オプションを使用して、出力を grep してください。

于 2014-12-29T06:07:50.190 に答える
1

関数をインライン化したくない場合は、オプションがあります

-fno-inline

「always_inline 属性でマークされた関数を除いて、関数をインライン展開しないでください。これは、最適化しない場合のデフォルトです」

また、インライン関数のサイズをより詳細に制御できます

-finline-limit=n

n命令数はどこですか

于 2014-12-28T05:57:50.777 に答える