13

mingw (Windows 用 gcc) でコンパイルする C++ プログラムがあります。gcc 4.4.1 を含む mingw の TDM リリースを使用します。実行可能ファイルは、2 つの静的ライブラリ (.a) ファイルにリンクしています。そのうちの 1 つは C で記述されたサードパーティ ライブラリです。もう 1 つは、私が作成した C++ ライブラリで、C ライブラリを使用して独自の C++ API を提供します。

C ライブラリの機能の (私の見解では、過度の) 一部がインライン関数で実装されています。C ライブラリの API を使用するときにインライン関数を含めることは避けられませんが、すべてをリンクしようとすると、すべてのインライン関数の複数の定義があるというリンク エラーが発生します。私のC++ラッパーライブラリと私が持っていないライブラリで呼び出された場合、基本的にヘッダーでインラインで定義されたものはすべて、CライブラリとC++ライブラリの両方で作成された関数を取得しています.

インクルード ファイルが同じプロジェクト内の異なる .c または .cpp ファイルで複数回使用されている場合、複数の定義エラーは発生しません。問題は、ライブラリごとに 1 つの定義が生成されることだけです。

コンパイラが両方のライブラリでこれらのインライン関数の関数とシンボルを生成する方法/理由は? コードでそれらの生成を強制的に停止するにはどうすればよいですか? .a ファイルから重複した関数を削除するために実行できるツール、またはリンカーに複数の定義を無視させる方法はありますか?

(参考までに、サードパーティのライブラリには、すべてのヘッダーに #ifdef __cplusplus および extern "C" ガードが含まれています。とにかく、それが問題である場合、シンボルの複数定義は発生しません。シンボルが定義されていないか、少なくとも異なります。)

特に、サード パーティの C ライブラリの DLL にリンクすると、リンク エラーは発生しません。ただし、DLLから呼び出す必要がある独自のバージョンの関数を持つコードに関係しているように見える奇妙なランタイムエラーが発生します。(あたかもコンパイラが私が求めていない関数のローカル バージョンを作成しているかのように。)

この質問の同様のバージョンが以前に尋ねられましたが、これらのいずれにおいても私の状況に対する答えが見つかりませんでした:

この質問に対する答えは、ポスターが変数を複数定義しているということでした。私の問題は、インライン関数の複数の定義です: 複数の cpps に同じヘッダーを含めることによる複数の定義エラーの繰り返し

これは MSVC プログラムでしたが、mingw を使用しています。また、この質問でのポスターの問題は、ヘッダー内のクラス本体の外側にある C++ クラス コンストラクターの定義でしたが、私の問題はインラインの C 関数にあります: Static Lib Multiple Definition Problem

この愚か者はすべての C コードの名前を C++ ファイルに変更しましたが、彼の C コードは C++ セーフではありませんでした: リンク時に多数の std:: 関数を複数定義する

これは、1 つの定義ルールに違反してもエラーにならない理由を知りたいだけでした: 定義が異なるインライン関数の予測できない動作

4

1 に答える 1

17

まず、C99 インライン モデルを理解する必要があります。ヘッダーに何か問題がある可能性があります。外部 (非静的) リンケージを持つインライン関数には、2 種類の定義があります。

  • 外部定義
    関数のこの定義は、指定された TU で、プログラム全体で 1 回だけ使用できます。他の TU から使用できるエクスポート機能を提供します。

  • インライン定義
    これらは、個別の定義として宣言されているすべての TU に表示されます。定義は、互いに同一である必要も、外部定義と同一である必要もありません。ライブラリの内部で使用すると、外部定義で行われる関数引数のチェックを省略できます。

関数の各定義には独自のローカル静的変数があります。ローカル宣言にはリンケージがないためです (C++ のように共有されません)。非静的インライン関数の定義は、次の場合にインライン定義になります。

  • TU 内のすべての関数宣言には指定子が含まれinline
  • TU 内の関数宣言には指定子が含まれていませんextern

それ以外の場合、その TU に表示する必要がある定義 (インライン関数は宣言された同じ TU で定義する必要があるため) は外部定義です。インライン関数の呼び出しでは、外部定義またはインライン定義のどちらが使用されるかは未指定です。ただし、すべてのケースで定義された関数は同じであるため (外部リンケージがあるため)、インライン定義がいくつ表示されても、そのアドレスはすべてのケースで等しくなります。そのため、関数のアドレスを取得すると、コンパイラが外部定義に解決される可能性があります (特に最適化が無効になっている場合)。

inline2 つの TU に関数の外部定義が 2 回含まれており、複数定義エラーが発生しているため、 の間違った使用を示す例

// included into two TUs
void f(void); // no inline specifier
inline void f(void) { }

次のプログラムは危険です。コンパイラは外部定義を自由に使用できますが、プログラムは外部定義を提供しないからです。

// main.c, only TU of the program
inline void g(void) {
  printf("inline definition\n");
}

int main(void) {
  g(); // could use external definition!
}

GCC を使用して、メカニズムをさらに実証するいくつかのテスト ケースを作成しました。

main.c

#include <stdio.h>

inline void f(void);

// inline definition of 'f'
inline void f(void) {
  printf("inline def main.c\n");
}

// defined in TU of second inline definition
void g(void);

// defined in TU of external definition
void h(void);

int main(void) {
  // unspecified whether external definition is used!
  f();
  g();
  h();

  // will probably use external definition. But since we won't compare
  // the address taken, the compiler can still use the inline definition.
  // To prevent it, i tried and succeeded using "volatile". 
  void (*volatile fp)() = &f;
  fp();
  return 0;
}

main1.c

#include <stdio.h>

inline void f(void);

// inline definition of 'f'
inline void f(void) {
  printf("inline def main1.c\n");
}

void g(void) {
  f();
}

main2.c

#include <stdio.h>

// external definition!
extern inline void f(void);

inline void f(void) {
  printf("external def\n");
}


void h(void) {
  f(); // calls external def
}

これで、プログラムは期待どおりの結果を出力します!

$ gcc -std=c99 -O2 main.c main1.c main2.c
inline def main.c
inline def main1.c
external def
external def

シンボル テーブルを見ると、インライン定義のシンボルは ( からmain1.o) エクスポートされていないのに対し、外部定義は ( からmain2.o) エクスポートされていることがわかります。


ここで、スタティック ライブラリのそれぞれにインライン関数の外部定義がある場合 (そうあるべきです)、それらは自然に互いに競合します。解決策は、インライン関数を静的にするか、単に名前を変更することです。これらは常に外部定義を提供します (完全な定義です) が、内部リンケージがあり、競合しないため、エクスポートされません。

static inline void f(void) {
  printf("i'm unique in every TU\n");
}
于 2010-02-07T19:43:32.920 に答える