1. ホーム
  2. C++

致命的なエラー LNK1169: 1つ以上の多重定義されたシンボルが見つかりました 解決策

2022-02-14 01:46:27

以下は、インターネットに掲載されている古典的な解析記事の転載です。

しかし、それらのいくつかは、まだ非常に明確ではありません、共有することを歓迎します。

致命的なエラー LNK1169: 1つ以上の多重定義されたシンボルが見つかりました。

コンパイルされた結合でよくある問題の1つに、異なるソースファイルでの変数の重複があります。例えば、project1には2つの.cまたは.cppファイルがあり、a.cとb.cと仮定します。 
1: 同名の変数が定義されている。 
2: 同じヘッダーファイルが含まれている(その中で非ローカル変数が定義されている)。  

これにより、コンパイルで生成される a.obj, b.obj ファイルにこの同じ変数のためのスペースが確保され、リンカーが名前チェックを行い、同じ名前が現れたら、次のように表示されます。

致命的なエラー LNK1169: 1つ以上の多重定義されたシンボルが見つかりました。  

解決策 最初のケースでは、他の重複する定義をexternalキーワードでマスクすれば十分です。実際、ケース2はより不明瞭で一般的であり、非ローカル変数の定義を避けることができるだけです。

ご存知のように、C/C++のソースプログラムから実行ファイルになるまでには2つの段階があります。(1) コンパイラがソースファイルをアセンブリコードにコンパイルし、アセンブラがマシン命令(およびその他の関連情報)に変換してターゲットファイル(オブジェクトファイル、VCコンパイラでコンパイルされたターゲットファイルのデフォルトサフィックスは.obj )に出力する; (2) リンカがオブジェクトファイル(およびおそらくいくつかのライブラリ)をリンクして完全な実行ファイルを作成する。
コンパイラはソースファイルをコンパイルし、ソースファイルのグローバルシンボルを強と弱の2種類に分けてアセンブラに渡し、アセンブラは強と弱の情報をエンコードしてターゲットファイルのシンボルテーブルに格納します。では、強弱とは何でしょうか。コンパイラは関数や初期化されたグローバル変数を強いシンボルとみなし、初期化されていないグローバル変数は弱いシンボルになります。たとえば、次のようなソースファイルがあったとする。
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{ <未定義
は0を返します。
}
mainとbufは強シンボル、pは弱シンボル、errornoは外部変数の使用宣言に過ぎないので強でも弱でもない。
強いシンボルと弱いシンボルという概念を念頭に置き、リンカが複数回定義されたグローバルシンボルをどのように扱い、選択するかを見てみましょう。
ルール1:強シンボルは複数回定義してはならない(つまり、異なるターゲットファイルに同じ名前の強シンボルを定義してはならない)。
ルール2:あるシンボルが1つのターゲットファイルで強く、他のすべてのファイルで弱い場合、強いシンボルを選択します。
ルール3:あるシンボルがすべてのターゲットファイルで弱い場合、そのうちのどれかを選択します。
上記から明らかなように、複数のターゲットファイルでは、同じ名前の関数や初期化されたグローバル変数を重複して使用することはできません。そうしないと、LNK2005とLNK1169という2つのリンクエラーになります。しかし、自分のプログラムではこのような再定義現象は見られず、このようなリンクエラーに遭遇することがあります。さて、この問題は少し複雑なので、ゆっくり説明しましょう。
ANSI C/C++では、相当数の標準関数が定義されており、それらは様々なターゲットファイルに分散されていることはよく知られています。もし、それらをターゲットファイルとして直接プログラマが利用できるとしたら、実行ファイルをうまく生成するために、どの関数がどのターゲットファイルに存在するかを正確に把握し、リンク時にターゲットファイル名を明示的に指定する必要があり、明らかに大きな負担となる。そこでC言語では、複数のターゲットファイルを1つのファイル(スタティックライブラリ)にパッケージングする仕組みを用意しています。開発者はリンク時にライブラリのファイル名を指定するだけで、リンカーは自動的にライブラリの中からアプリケーションが使用するターゲットモジュールを探し出し、実行ファイルの構築に参加するためにライブラリからコピーする(コピーするだけ)のです。ほとんどすべてのC/C++開発システムは、開発者が利用できるように標準関数を標準ライブラリにパッケージしている(そうでないことはない?
ライブラリは開発者にとって便利なものですが、混乱の元にもなっています。ここでは、リンカーがライブラリへの参照をどのように解決するか見てみましょう。
シンボル解決フェーズでは、リンカはすべてのターゲットファイルとライブラリファイルをコマンドラインに表示された順に左から右へスキャンし、その間、いくつかのセットを維持します。(1)セットEは、実行ファイルを作成するためにマージされるすべてのターゲットファイルのセットです。(2)セットUは、未解決シンボル(例えば、参照されているがまだ定義されていないシンボル)のセットです。
(1): コマンドラインの各入力ファイルfに対して、リンカはそれがターゲット・ファイルかライブラリ・ファイルかを判断する。ターゲット・ファイルであれば、fをEに追加し、f中の未解決シンボルをUセット、定義済みシンボルをDセットにそれぞれ追加し、次の入力ファイルを処理する。
(2): f がライブラリファイルの場合、リンカは U の未解決シンボルをすべて f の各ターゲットモジュールで定義されたシンボルとマッチングしようとする。ターゲットモジュール m が U の未解決シンボルを定義している場合、m は E に加えられ、m の未解決シンボルおよび定義済みシンボルがそれぞれ U および D のセットに加えられる。この処理をfのすべての対象モジュールについて、ある定点に達するまで連続的に繰り返し、その時点でUとDはもはや変化しなくなる。Eに追加されなかったfのターゲットモジュールは単に破棄され、リンカーは次の入力ファイルの処理を続ける。
(3): 処理中にDに既存のシンボルが追加された場合、またはすべての入力ファイルをスキャンしたときにUが空でない場合、リンカはエラーを報告して動作を停止させる。それ以外の場合は、Eにあるすべてのターゲットファイルをマージして実行ファイルを生成する。
VCに付属するコンパイラ名はcl.exeで、標準ライブラリに関連するオプションは以下のとおりです。/ML, /MLd, /MT, /MTd, /MD, /MDd。これらのオプションは、アプリケーションが使用したいC標準ライブラリのバージョンをコンパイラに伝えるものです。/ML (デフォルト・オプション) はシングルスレッド・スタティック・バージョンの標準ライブラリ (libc.lib) に対応し、/MT はマルチスレッド・スタティック・バージョンの標準ライブラリ (libcmt.lib) に対応し、コンパイラは _MT マクロを自動的に定義します。/MD はマルチスレッド・ DLL バージョン (インポート・ライブラリ msvcrt.lib, DLL は msvcrt.dll) に対応し、コンパイラによって _MT と _DLL 2 マクロを自動的に定義します。d の後にオプションが続くと、コンパイラは自動的に _DEBUG マクロを追加定義し、対応する標準ライブラリのデバッグ版を使用することを示します。したがって、/MLd はシングルスレッド静的標準ライブラリのデバッグ版 (libcd.) に対応します。 lib)、/MTd はマルチスレッドのスタティック標準ライブラリ(libcmtd.lib)のデバッグ版に対応し、/MDd はマルチスレッドの DLL 標準ライブラリ(import library msvcrtd.lib, DLL は msvcrtd.dll) のデバッグ版に対応するように定義されています。コンパイル時にアプリケーションが使用したい標準ライブラリのバージョンをコンパイラに正確に伝えますが、コンパイラの作業が終わった後、リンカが作業する番が来たときに、ターゲット・ファイルの行方不明者をどうやって知るのでしょうか。愛情を伝えるために、私たちのコンパイラは少し秘密の仕事をしています。clコンパイルされたターゲットファイルでは、どのように動作するかをリンカを導くためにいくつかの情報を格納するための特別な領域(最後の友人のファイルのどこにCOFFとPEファイル形式を参照することができます懸念)を持って、それらの一つは、デフォルトライブラリ(デフォルトライブラリ)と呼ばれ、この情報は、1つまたは複数のライブラリファイルの名前を指定し、スキャンするリンカに伝えるときにもこの情報(もちろん、コマンドライン上で指定した入力ファイルの後に)入力ファイルのリストにそれらを追加するにはリンカに通知します。そういえば、ちょっとした実験をしてみましょう。非常に簡単なプログラムを書いて、main.cという名前で保存してください。
/* main.c */
int main() { return 0; }.
次のコマンドでmain.cをコンパイルしてください(えっ、あなたはプログラムをコンパイルするのにコマンドラインを使ったことがないのですか? これは......) :
cl /c main.c
/c は cl にソースファイルだけをコンパイルし、リンクはしないように指示しています。MLはデフォルトのオプションなので、上のコマンドは次のコマンドとも等価です: cl /c /ML main.c . もし何も問題がなければ(そしてそれはもっとひどいことになります!) もちろん、環境変数が正しく設定されていない場合は別ですが、その場合はVCのbinディレクトリに移動してvcvars32.batファイルを見つけて実行してください)。カレントディレクトリに、main.obj ファイルが表示されます。これをテキストエディタで開き(そう、テキストエディタです、大胆に、恐れず)、"defaultlib" という文字列を探せば、たいてい次のようなものが見つかるでしょう。嗚呼、そうだ、その通りだ
は、ターゲットファイルに保存されているデフォルトのライブラリ情報です。このターゲットファイルでは、標準ライブラリ libc.lib のシングルスレッド静的バージョン (/ML オプションと一致) と oldnames.lib (Microsoft の以前の C/C++ 開発システムと互換性があることを意味) の 2 つのデフォルトライブラリが指定されているようです。
VCのリンカはlink.exeで、main.objがデフォルトのライブラリ情報を持っているので、それを利用することができます
リンク main.obj libc.lib
または
リンク main.obj
を実行して実行ファイルmain.exeを生成する場合、この2つのコマンドは等価です。しかし、もし
リンク main.obj libcd.lib
リンカは警告を出します: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library" なぜならあなたが明示的に指定した標準ライブラリのバージョンがターゲットファイルのデフォルト値と一致しない からです。一般に、リンカによってマージされるすべてのターゲット・ファイルに指定されるデフォルトの標準ライブラリのバージョンが同じであることを確認する必要があります。そうでなければ、コンパイラは上記の警告を必ず出しますが、LNK2005やLNK1169のリンク・エラーは出るときと出ないときがあります。では、具体的にどのような時に出るのでしょうか?ああ、心配しないで、以下はすべて、物事の真相を究明するのが好きな人のためだけのものです。
以下の内容のソースファイル(mylib.c)を作成します。
/* mylib.c */
#i nclude 
void foo()
{ <未定義
printf("%s","I am from mylib!/n")を実行します。
}
使用方法
cl /c /MLd mylib.c
コマンドでコンパイルします。 /MLd オプションでデフォルトの標準ライブラリとして libcd.lib を指定していることに注意してください。 lib.exe は VC に付属するコマンドで、ターゲットファイルをライブラリにパッケージングするため、このコマンドで
lib /OUT:my.lib mylib.obj
mylib.objをライブラリとしてパッケージ化し、出力ライブラリファイル名をmy.libとします。
/* main.c */
void foo()。
int main()
{ <未定義
foo()です。
は0を返します。
}

使用方法
cl /c main.c
コンパイルして
リンク main.obj my.lib
リンク このコマンドは LNK2005 と LNK1169 リンクエラーを発生させることなく main.exe を正常に生成し、警告メッセージ:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library" を受け取るだけでした。この時点でリンカが何をしているのか、先に説明したスキャン・ルールに基づいて分析してみましょう。
最初、E,U,Dはすべて空の集合です。リンカはまず main.obj をスキャンして E のセットに加え、未解決の foo を U に、main を D に加えます。また main.obj のデフォルト標準ライブラリは libc.lib なので、現在の入力ファイルリストの末尾に追加します。そして my.lib がスキャンされ、これはライブラリなので、現在の U にあるすべてのシンボル (もちろん今は foo がひとつだけあります) を取り出し、それらを my.lib のすべてのターゲットモジュール (もちろん mylib.obj はひとつだけあります) と順にマッチさせて、U のシンボルを定義するモジュールがあるかどうかを確認します。 同様に mylib.obj によって指定されたデフォルトの標準ライブラリは libcd.lib で、これも現在の入力ファイルのリストの最後 (libc.lib の後) に追加されます。U と D のどちらも変更されなくなるまで、U のシンボルにマッチする my.lib ライブラリの モジュールを繰り返し続けます。明らかに、そのような不動点に今到達したので、次の入力ファイルをスキャンします。それは libc.lib です。リンカは、printf が libc.lib の printf.obj で定義されていることを発見し、printf は U から D に移動し、printf.obj は E に加えられました。printf が定義するすべてのシンボルは D に、その中の未解決シンボルは U に加えられました。また、リンカは、すべてのプログラムで何らかの初期化操作が使われている対象モジュール(crt0.objなど)と、それらが参照しているモジュール(malloc.obj、free.objなど)を自動的にEに追加し、この変更に合わせてUとDを更新する。実際には、標準ライブラリの各対象モジュールの未解決シンボルは、ライブラリ内の他のモジュールで定義されていることが分かるので、リンカがlibc.libの処理を終了した時点でUは空でなければならない。最後に libcd.lib が処理されますが、この時点で U は空なので、リンカはその中のターゲットモジュールをすべて破棄してスキャンを終了し、ターゲットモジュールを E にマージして実行形式を出力します。
上記は、ターゲットモジュールごとに異なるバージョンのデフォルト標準ライブラリを指定してもリンクに成功した例であり、この厳密性の欠如から生じた悲劇的な失敗を目の当たりにすることになる。
mylib.cを次のように修正します。
#nclude 
void foo()
{ <未定義
// メモリリークを気にしないテストです。
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );
}
malloc_dbgはANSI Cの標準ライブラリ関数ではなく、VC標準ライブラリで提供されるmallocのデバッグ版で、開発者が関連関数で様々なメモリエラーをキャッチできるようにするためのものです。これは、VC標準ライブラリによって提供されるmallocのデバッグバージョンです。
cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj
コンパイルしてパッケージ化します。で再コンパイルすると
リンク main.obj my.lib
リンクを行うと何が見えるか?リンカはおかしいですよね?LNK2005とLNK1169はリストの一番下にあり、もちろんLNK4098はありません。
最初、E,U,Dは空です。リンカーは main.obj をスキャンして E に追加し、foo を U に追加して main を D に追加し、 libc.lib を現在の入力ファイルリストの末尾に追加しています。次に my.lib がスキャンされ、foo が U から D に移動され、_malloc_dbg が U に追加され、libcd.lib が現在の入力ファイルのリストの最後に追加されます。次に libc.lib がスキャンされ、_malloc_dbg が libc.lib のどのターゲットモジュールでも定義されていないことがわかりました (標準ライブラリのデバッグ版にのみ存在します)。したがって、_malloc_dbg のために E に追加されるモジュールはありませんが、すべてのプログラムが使用する初期化モジュール (crt0. obj など) と、それらが参照するモジュール (malloc.obj, free.obj など) は、これまで通り自動的に E に追加され、U と D はこの変更を反映するために更新されます。最後に libcd.lib が処理されると、dbgheap.obj が _malloc_dbg を定義していることがわかり、 dbgheap.obj は E に、その未解決シンボルは U に、その定義する他のすべてのシンボルは D に追加されます。malloc などのシンボルはすでに D にあり(libc.lib の malloc.obj で E に追加)、さらに dbgheap.obj は malloc などの同名のシンボルを多数定義しているので、再定義の衝突が起こり、リンカは壊れてエラーを報告しなければならなくなったのです。
もうお分かりだと思いますが、リンカーには全く責任はなく、責任は私たちにあるのです。ターゲットファイル(main.obj)とプログラムライブラリ(my.lib)を不注意でリンクしてしまい、デフォルトの標準ライブラリのバージョンが一致しなかったことが、大惨事を招いたのです。解決策は簡単で、main.cを/MLdオプション付きで再コンパイルするか、mylib.cを/MLオプション付きで再コンパイルすればよいのです。
上記の例では、ライブラリmy.libのソースコード(mylib.c)があるので、そのソースコードを別のオプションで再コンパイルして、再びパッケージ化することができます。しかし、ソースコードが提供されていないサードパーティのライブラリを使用する場合、これらのライブラリに合わせて自作プログラムのコンパイルオプションを変更しなければなりません。しかし、ライブラリ内の対象モジュールが指定するデフォルトのライブラリをどうやって知ることができるのだろうか?実は、VCはこの作業を行うための小さなツールを提供しています。それがdumpbin.exeです。次のコマンドを実行します。
dumpbin /DIRECTIVES my.lib
そして、出力された "Linker Directives" のブートストラップメッセージを探せば、きっとそれぞれのメッセージに "-defaultlib:XXXX" のような文字列がいくつか含まれていることに気づくでしょう。ここで XXXX はターゲットモジュールで指定されたデフォルトライブラリ名です。
サードパーティライブラリが指定するデフォルトの標準ライブラリを知り、適切なオプションでアプリケーションをコンパイルすれば、LNK2005やLNK1169のリンクエラーを回避することができるのです。IDEが好きな人は、"Project properties" -> "C/C++" -> "code generation" -> "run-time library" でアプリケーションのデフォルト標準ライブラリを設定することもできます。 quot; 項目はアプリケーションのデフォルト標準ライブラリバージョンを設定して、コマンドラインのオプションと同じ効果があります。
究極の解決策
プロジェクト/設定/リンク/一般で、プロジェクト・オプションに/FORCE:MULTIPLEを追加します。