LNK1120:未解決の外部参照は直前の LNK2019 / LNK2001 を見る

LNK1120:未解決の外部参照は直前の LNK2019 / LNK2001 を見る

ビルドの最後にfatal error LNK1120: 2件の未解決の外部参照 と出て、exeができません。件数だけ言われても、どこを直せばいいのかは分かりませんよね。

見るべきは、その1つ上に並んでいるLNK2019とLNK2001の行です。出力ウィンドウを少し上へスクロールしてみてください。

目次

LNK1120の意味

公式ドキュメントにも、そう書いてあります。「エラーLNK1120は、現在のリンク内の未解決の外部シンボル エラーの数を報告します。」しかも「このエラーを修正する必要はありません。」とまで書いてあります。

未解決のシンボルは、まずLNK2001かLNK2019で報告されます。LNK1120が出るのはそのあとで、数を数えて見せているだけです。上の行を片づけていけば、LNK1120も勝手に消えます。

エラー行に出る2つの名前

宣言だけを書いて、定義をどこにも置かない関数と変数を用意しました。それをmainから参照してリンクすると、Visual Studio 2026 / Debug / x64ではこうなりました。長いパスが並ぶと読みにくいので、行末のプロジェクトのパスは省いてあります。ファイル名も短いものに置き換えました。

main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl ReportShortage(int)" (?ReportShortage@@YAXH@Z) が関数 main で参照されました
main.obj : error LNK2001: 外部シンボル "int g_nStockThreshold" (?g_nStockThreshold@@3HA) は未解決です
MyApp.exe : fatal error LNK1120: 2 件の未解決の外部参照

行頭にあるのは、その参照を持っている .objです。関数のほうはLNK2019で、参照元のmainまで名指しされています。変数のほうはLNK2001で、参照元は出ていません。同じファイルの同じ関数から参照していても、行の形は変わります。

どちらの行にも、同じシンボルの名前が2つ並んでいます。”void __cdecl ReportShortage(int)” がソースに書いた形で、かっこの中の ?ReportShortage@@YAXH@Zが装飾名です。公式ドキュメントでも「”フレンドリ名” (ソース コードで使用される名前) と装飾名 (かっこ内) の両方が表示されます。」と説明されています。

リンカーが探しているのは、かっこの中のほうです。暗号のように見えますが、中身が読めなくても困りません。公式も「装飾名の解釈方法を理解している必要はありません。 検索して、他の装飾名と比較することができます。」と書いています。他の装飾名と見比べられれば十分、ということです。なお、リンカーが装飾名しか報告できない場合もあります。

装飾名の正体

装飾名は、関数名をそのまま写したものではありません。公式の説明では「関数のシンボル名では、その戻り値の型、パラメーターの型、スコープ、呼び出し規則などがエンコードされます。」となっています。つまり、宣言と定義がここで食い違うと、リンク エラーが起きることがあります。

では、どこを変えると名前が変わるのでしょうか。中身が同じ関数を6本用意して、宣言だけを1か所ずつ変えてみました。それぞれの装飾名を並べたのが次の画面です。

宣言を1か所ずつ変えた6本の関数について、ソースに書いた形と装飾名を並べたMFC実行結果の一覧
左が宣言で変えたところ、中央がソースに書いた形、右が .objに入る装飾名(Visual Studio 2026 / Debug / x64 / Unicode)
宣言で変えたところ装飾名に出た違い(実測)
extern “C” を付けた装飾されず、関数名がそのまま入る
引数をLPCSTR / LPCWSTRにPEBDとPEB_Wに分かれる
引数をLPCTSTRにこのUnicode構成ではPEB_W側になった
__stdcallを付けたx64では既定と同じ符号のまま変わらなかった

最後の行は意外かもしれません。でも仕様どおりです。__stdcallの公式ページに「ARMおよびx64プロセッサでは、__stdcallはコンパイラによって受け入れられるか無視されます。」とあります。x64では、__cdeclと __stdcallを書き分けても装飾名は変わりません。この2つの取り違えが名前に出るのはx86のときです。ただし __vectorcallのようにx64でも効く規約はあるので、呼び出し規約そのものを候補から外さないでください。

LPCTSTRが構成で変わるのは、型の定義がそうなっているからです。Windowsデータ型の表はLPCTSTRを「UNICODEが定義 場合はLPCWSTR、それ以外の場合はLPCSTR」と書いています。C++ の関数の引数にLPCTSTRを使っていると、UNICODEの有無が違うライブラリを混ぜたときに装飾名がそろいません。いっぽうextern “C” の関数名には、引数の型が入りません。ですから、この食い違いは名前には出てきません。

名前ごとに見る場所が変わる

フレンドリ名を読めば、それが誰の関数かは分かります。そこから先は、名前の出どころで分かれます。

未解決になった名前最初に見る場所
自分で書いた関数・変数定義を書いた .cppがプロジェクトのビルド対象に入っているか
ライブラリの関数[リンカー] > [入力] > [追加の依存ファイル] にその .libがあるか
クラスのstaticメンバーどこか1つの .cppに定義を書いたか
受け取った .libの中の名前その .libがx64とx86のどちら用にビルドされているか
名前が惜しいところで食い違う引数の型・extern “C”・呼び出し規約

公式が「考えられる原因」の先頭に置いているのも、この表の1行目と2行目にあたる原因です。「シンボルの定義を含むソース ファイルがコンパイルされていません」「シンボルの定義を含むオブジェクト ファイルまたはライブラリがリンクされていない」の順で並んでいます。アーキテクチャについても「コードにリンクされているライブラリとオブジェクト ファイルは、コードと同じアーキテクチャ用にコンパイルする必要があります。」と書かれています。

さきほどのエラー行を出した宣言は、ヘッダーに置いたこの2行だけです。定義はどこにもありません。

void ReportShortage(int nCount);
extern int g_nStockThreshold;

これでもコンパイルは通ります。公式の説明どおり、コンパイラは宣言されていないシンボルは見つけられても、定義されていないシンボルは見つけられません。定義が別のソース ファイルやライブラリにあるかもしれないからです。止まるのはリンクの段階です。LNK1120の「2件」は、この2つの宣言に対応していました。

それでも通らないときに使うコマンド

定義の入ったファイルがビルドに含まれているかは、/VERBOSEリンカー オプションで見られます。公式の説明は「リンカーがどのファイルを参照しているかを確認できます」です。名前そのものを調べるならDUMPBINの /SYMBOLS、装飾名を元の形に戻すならUNDNAMEです。

/SYMBOLSの出力は、名前があるかどうかだけでは読めません。公式は「3番目の列にSECTxが含まれている場合は、オブジェクト ファイルのそのセクションにシンボルが定義されます。 ただし、UNDEFが表示されている場合は、そのオブジェクトで定義されていないため、他の場所で解決する必要があります。」と説明しています。5番目の列(Static / External)も見ます。Staticのシンボルは外から参照できません。名前が一致していてもリンクは通りません。なお /GLでビルドしたファイルには /SYMBOLSを使えません。

ここまでは、リンカーが定義を見つけられないときの話でした。ファイルそのものを開けないLNK1104は、原因の層が違います。直し方はLNK1104 / LNK2019 / LNK2001の記事に分けてあります。定義が多すぎるLNK2005は逆向きの問題で、LNK2005 / LNK1169の記事で扱っています。

確かめた構成は、出力ごとに違います。上のエラー行は、MFCを使わないコンソール アプリで出しました。装飾名の一覧のほうはMFC共有DLLのダイアログ アプリです。共通なのはVisual Studio 2026 / Debug / x64 / Unicodeです。ビルドに使ったのはMSBuild 18.9.1.35102、プラットフォーム ツールセットはv145でした。装飾名の中身は、この版と構成で変わります。

まとめ

  • 件数だけを読んで、出力ウィンドウを上へスクロールする。直す行はLNK2019とLNK2001のほう。
  • 行に並んだ名前のうち、かっこの中の装飾名を見る。リンカーが探しているのはこちら。
  • フレンドリ名で出どころを決める。自分の関数なら定義とビルド対象、ライブラリの関数なら [追加の依存ファイル] とアーキテクチャ。
  • 名前が惜しいところで食い違うなら、引数の型とextern “C” を見る。__cdeclと __stdcallの取り違えはx64では名前に出ない。
目次