コンパイルは通ったのに、ビルドの最後でLNK2019が出て、実行ファイルができません。エラー文には「未解決の外部シンボル」という言葉と、?add@@YAHHH@Zのような、暗号めいた文字列が並んでいます。
LNK2019は、リンカーが「呼ばれている関数や変数の実体」を、どこにも見つけられなかったときのエラーです。原因は1つではなく、よくあるものだけでも何通りもあります。そこで、MSVCで原因を7パターンわざと再現して、実際に出たエラー文と、直したあとの実行結果を並べました。エラー文のどこを読めば原因が絞れるのかも、実物で確かめています。
検証は、Visual Studio 2022付属のコンパイラ(MSVC 19.44、x64)を、コマンドラインのclで動かして行いました。Visual Studioのビルドでも、同じリンカーが同じ形式のエラー文を出します。
LNK2019とは?
LNK2019とは、
・コンパイルは通ったのに、リンカーが「呼ばれている関数や変数の実体」をどこにも見つけられなかったときに出るエラー
です。C++のビルドは、大きく2段階に分かれています。
- コンパイル:ソースファイル(.cpp)を1つずつ、機械語の部品(.obj)に変換する。このとき、他のファイルにある関数は「宣言」があれば呼び出してよい。実体はあとで誰かが探してくれる前提で、呼び出し部分には「ここに
addを入れてね」という穴を残す - リンク:すべての.objとライブラリ(.lib)を集めて、穴を1つずつ埋めて実行ファイル(.exe)にする。穴に入れる実体がどこにも無いと、この段階で失敗する
つまり、LNK2019は「宣言はあったので、コンパイルは通した。でも実体が見つからなかった」という意味です。構文エラー(C2143など)のように、書き方が間違っているわけではありません。「実体が、リンクの対象に入っていない」ことを疑うエラーです。
エラー文の読み方
エラー文は、見慣れないと暗号のように見えます。後述する原因④(宣言と定義の型が違う)で実際に出たエラー文に、読み方を書き込みました。

- シンボル:見つからなかった関数や変数の名前。
"int __cdecl add(int,int)"のように、戻り値と引数の型まで含めた名前で出る - 括弧の中の長い文字列:C++が内部で使う「装飾名」。型の情報が名前に埋め込まれている
- 「関数 main で参照されました」:見つからない名前を、どこが呼んでいるか。ここを手がかりに、呼び出し側のコードを探せる
- 「ヒント」:リンカーが、似た名前の定義を見つけたときだけ出る(出ないことも多い)
装飾名は、MSVCに付属するundnameで人間が読める形に戻せます。実際に3つ戻してみました。
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.
Undecoration of :- "?add@@YAHHH@Z"
is :- "int __cdecl add(int,int)"
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.
Undecoration of :- "?add@@YAHHN@Z"
is :- "int __cdecl add(int,double)"
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.
Undecoration of :- "??$maxOf@H@@YAHHH@Z"
is :- "int __cdecl maxOf<int>(int,int)"?add@@YAHHH@Zがint add(int,int)、?add@@YAHHN@Zがint add(int,double)に戻りました。C++では、関数名に引数の型が埋め込まれます。だから、型が違う関数は、別の名前の別物として扱われます。この性質が、後述する原因④の正体です。
原因を7パターン再現する
ここからは、原因を1つずつ、わざと起こして確かめます。それぞれ「壊したコード」「出たエラー」「直し方と実行結果」の順です。
①宣言だけ書いて、定義を書いていない
もっとも基本の原因です。関数の宣言だけがあって、実体がどこにもありません。
▼main.cpp
#include <cstdio>
int add(int a, int b); // 宣言だけ。定義はどこにも書いていない
int main() {
std::printf("%d\n", add(1, 2));
return 0;
}main.cpp
main.obj : error LNK2019: 未解決の外部シンボル "int __cdecl add(int,int)" (?add@@YAHHH@Z) が関数 main で参照されました
main.exe : fatal error LNK1120: 1 件の未解決の外部参照エラー文のシンボルint add(int,int)が、そのまま「実体が無い関数」です。直すには、どこかに定義を書きます。
▼util.cpp(追加した)
int add(int a, int b) {
return a + b;
}cl main.cpp util.cppのように、定義を書いたファイルも一緒にビルドすると、通りました。
3②定義を書いたcppを、ビルドに含めていない
定義はちゃんと書いてあるのに、そのファイルをビルドの対象にしていないパターンです。clで直接ビルドしているときは、コマンドにファイル名を書き忘れます。
▼main.cpp
#include <cstdio>
#include "util.h"
int main() {
std::printf("%d\n", add(1, 2));
return 0;
}▼util.cpp(定義はここにある)
#include "util.h"
int add(int a, int b) {
return a + b;
}cl main.cpp ← util.cpp が入っていないmain.cpp
main.obj : error LNK2019: 未解決の外部シンボル "int __cdecl add(int,int)" (?add@@YAHHH@Z) が関数 main で参照されました
main.exe : fatal error LNK1120: 1 件の未解決の外部参照①とまったく同じ文面でした。エラー文だけでは、「定義を書き忘れた」のか「書いたのにビルドに入っていない」のか区別できません。Visual Studioでは、プロジェクトのソースファイル一覧に、そのcppが入っているかを確認します(プロジェクト外のフォルダに置いただけのファイルは、ビルドされません)。直すには、cl main.cpp util.cppのように、ファイルを加えます。
3③ライブラリを指定していない
自分のコードではなく、Windows APIなどの外部ライブラリの関数を使うときに出るパターンです。例として、前に書いたSleepの精度の記事でも使うtimeBeginPeriodを呼びます。この関数の実体は、winmm.libというライブラリの中にあります。
▼main.cpp
#include <windows.h>
#include <mmsystem.h>
#include <cstdio>
int main() {
timeBeginPeriod(1); // 実体は winmm.lib の中にある
std::printf("ok\n");
timeEndPeriod(1);
return 0;
}main.cpp
main.obj : error LNK2019: 未解決の外部シンボル __imp_timeBeginPeriod が関数 main で参照されました
main.obj : error LNK2019: 未解決の外部シンボル __imp_timeEndPeriod が関数 main で参照されました
main.exe : fatal error LNK1120: 2 件の未解決の外部参照シンボル名の頭に__imp_が付いています。これは「DLLから取り込む関数」という印です。外部ライブラリの関数で、この__imp_が付いたLNK2019が出たら、ライブラリ(.lib)の指定漏れを疑います。直し方は2通りあります。
cl main.cpp winmm.lib▼方法2: コードの中で指定する(main_pragma.cpp)
#include <windows.h>
#include <mmsystem.h>
#include <cstdio>
#pragma comment(lib, "winmm.lib") // ライブラリをコードの中から指定する
int main() {
timeBeginPeriod(1); // 実体は winmm.lib の中にある
std::printf("ok\n");
timeEndPeriod(1);
return 0;
}okVisual Studioなら、プロジェクトのプロパティの「リンカー → 入力 → 追加の依存ファイル」にwinmm.libを足すのが、一般的な方法です。どのライブラリに関数が入っているかは、MicrosoftのAPIドキュメントの「要件」欄に書かれています。
④宣言と定義で、引数の型が違う
一番見つけにくいのが、これです。ヘッダーではint add(int, int)と宣言しているのに、cppではint add(int, double)と書いてしまいました。
▼util.h
#pragma once
int add(int a, int b); // int, int で宣言▼util.cpp(引数の型が違う)
#include "util.h"
int add(int a, double b) { // こちらは int, double で定義(別の関数になってしまう)
return a + (int)b;
}main.cpp
util.cpp
コードを生成中...
main.obj : error LNK2019: 未解決の外部シンボル "int __cdecl add(int,int)" (?add@@YAHHH@Z) が関数 main で参照されました
定義済みの一致する可能性があるシンボルに関するヒント:
"int __cdecl add(int,double)" (?add@@YAHHN@Z)
main.exe : fatal error LNK1120: 1 件の未解決の外部参照今回はユーティリティのファイルもビルドに入れています。それでもint add(int,int)が見つかりません。なぜなら、C++では引数の型が違う関数は、別の関数として扱われるからです。cppで定義したのはadd(int,double)であって、ヘッダーで宣言したadd(int,int)は、どこにも定義されていません。
今回は、リンカーが「ヒント」としてadd(int,double)を示してくれました。ヒントが出ないときは、dumpbinで.objの中身を直接見ます。
cl /c util.cpp
dumpbin /symbols util.obj | findstr add009 00000000 SECT3 notype () External | ?add@@YAHHN@Z (int __cdecl add(int,double))このobjには、add(int,double)しか入っていません。呼び出し側が探しているadd(int,int)とは、型が違うことが分かります。直すには、型をヘッダーとそろえます。
▼util.cpp(直した)
#include "util.h"
int add(int a, int b) { // ヘッダーと同じ引数の型にそろえる
return a + b;
}3⑤テンプレートの定義を、cppに書いた
テンプレート関数の定義を、通常の関数と同じ感覚でcppに書いて、ヘッダーには宣言だけを置いたパターンです。
▼maxof.h
#pragma once
template <typename T>
T maxOf(T a, T b); // 宣言だけをヘッダーに置いている▼maxof.cpp
#include "maxof.h"
template <typename T>
T maxOf(T a, T b) { // 定義はcppの中。ここでは誰もmaxOf<int>を使っていない
return a > b ? a : b;
}main.cpp
maxof.cpp
コードを生成中...
main.obj : error LNK2019: 未解決の外部シンボル "int __cdecl maxOf<int>(int,int)" (??$maxOf@H@@YAHHH@Z) が関数 main で参照されました
main.exe : fatal error LNK1120: 1 件の未解決の外部参照エラー文のシンボルはmaxOf<int>(int,int)です。テンプレートは、使われた型ごとに、使う側のファイルで中身を生成する仕組みです。maxof.cppの中では、誰もmaxOf<int>を使っていないので、int版が作られません。main.cppからは、定義が見えないので、作れません。結果、int版は、どこにも存在しません。直し方は、定義ごとヘッダーに置くことです。
▼maxof.h(直した)
#pragma once
template <typename T>
T maxOf(T a, T b) { // テンプレートは定義ごとヘッダーに置く
return a > b ? a : b;
}7⑥staticメンバ変数の実体を、書いていない
クラスのstaticメンバ変数は、クラスの中に書くのは「宣言」で、実体はクラスの外に1回だけ書く必要があります。
▼main.cpp
#include <cstdio>
class Counter {
public:
static int count; // 宣言だけ。クラスの外に定義が必要
static void Up() { count++; }
};
int main() {
Counter::Up();
std::printf("%d\n", Counter::count);
return 0;
}main.cpp
main.obj : error LNK2019: 未解決の外部シンボル "public: static int Counter::count" (?count@Counter@@2HA) が関数 main で参照されました
main.exe : fatal error LNK1120: 1 件の未解決の外部参照staticメンバ変数なので、LNK2001という別のエラー番号が出ると予想していました。ところが、実際に出たのはLNK2019でした。関数から参照している変数が見つからないときも、LNK2019になるようです。グローバル変数をexternで宣言して実体を書かなかったときも、同じでした。
main.cpp
main.obj : error LNK2019: 未解決の外部シンボル "int g_score" (?g_score@@3HA) が関数 main で参照されました
main.exe : fatal error LNK1120: 1 件の未解決の外部参照直し方は、クラスの外に実体を1回だけ書くことです。
▼main2.cpp(追加した行)
int Counter::count = 0; // クラスの外で実体を1回だけ定義する1⑦仮想関数の定義を、書き忘れた
最後は、ちょっと意地悪なパターンです。クラスのvirtual関数を宣言して、定義を書き忘れました。この関数は、どこからも呼んでいません。
▼main.cpp
#include <cstdio>
class Enemy {
public:
virtual void Update(); // 宣言だけ。定義を書き忘れている
};
int main() {
Enemy e;
std::printf("ok\n");
return 0;
}main.cpp
main.obj : error LNK2001: 外部シンボル "public: virtual void __cdecl Enemy::Update(void)" (?Update@Enemy@@UEAAXXZ) は未解決です
main.exe : fatal error LNK1120: 1 件の未解決の外部参照呼んでいないのに、リンクエラーになりました。仮想関数は、使わなくても、クラスの「仮想関数テーブル」に載るので、実体が必要になるからです。しかも、エラー番号が今までのLNK2019ではなくLNK2001で、文面も「関数 main で参照されました」ではなく「未解決です」だけでした。今回の環境では、「関数の呼び出し」から参照されたものがLNK2019、仮想関数テーブルのような、関数の外からの参照がLNK2001でした。直し方は、定義を書くことです(派生クラスで上書きするなら、基底クラスは= 0の純粋仮想にする手もあります)。
▼main2.cpp(追加した定義)
void Enemy::Update() { // 定義を書く
std::printf("update\n");
}update
ok【重要】再現して分かった罠:①と②は、エラー文が完全に同じ
7つ再現して、いちばん困ったのは、原因が違うのに、エラー文が一字一句同じことでした。①(定義を書いていない)と②(定義はあるが、ビルドに入っていない)は、どちらも次の文面です。
main.obj : error LNK2019: 未解決の外部シンボル "int __cdecl add(int,int)" (?add@@YAHHH@Z) が関数 main で参照されましたエラー文を見て「定義を書いていないんだな」と思い込んで、ソースの中を探し続けても、見つかりません。②は、ソースをいくら見ても、定義があるからです。
そういうときは、次の順で疑うと早いです。
- そのシンボルの定義が、ソース全体のどこかにあるかを検索する(なければ①)
- あるなら、そのファイルが、ビルドの対象に入っているかを確認する(入っていなければ②)
- 入っているなら、引数の型や
constが、宣言とそろっているかを見る(④)。ヒントが出なければdumpbinで.objの中身を直接見る
エラー文から、原因を絞り込む早見表
| エラー文の特徴 | まず疑うこと | 該当する原因 |
|---|---|---|
| 自分で書いた関数が見つからない | 定義を書いていない/cppがビルドに入っていない | ①② |
シンボル名の頭に__imp_が付いている | ライブラリ(.lib)の指定漏れ | ③ |
| 「ヒント」に、引数の違う同名の関数が出る | 宣言と定義で、引数の型やconstが違う | ④ |
シンボル名に<int>のような型がある | テンプレートの定義が、ヘッダーに無い | ⑤ |
public: staticと出る | クラスのstaticメンバの実体を書いていない | ⑥ |
| LNK2001で「呼んでいないのに」出る | 仮想関数の定義を書き忘れた | ⑦ |
LNK2019のよくある失敗例と対処法
①エラー文を見て、すぐ呼び出し側のコードを直そうとする
LNK2019は、呼び出し側が間違っているわけではなく、実体が足りないことを伝えています。呼び出し側のコードをいじる前に、「実体はどこにあるはずか」を探します。
②ビルドに入っているはずだと思い込む
特に、新しくcppを追加した直後に起きやすい原因です。ファイルを足したら、ビルド対象に入っているかを、先に確認します。
③型違いに気づかず、何度もビルドし直す
引数の型やconst、参照の有無が1つ違うだけで、別の関数になります。ヒントが出なくても、undnameとdumpbinで、「探されている名前」と「定義されている名前」を並べて比べると、一瞬で分かります。
注意点
- LNK2019は「実体が、リンクの対象に無い」というエラー。構文ミスではない
- ①と②は同じ文面になる。「定義があるか」「ビルドに入っているか」を順に確認する
- 外部ライブラリの関数で
__imp_が付いたら、.libの指定漏れ - C++は引数の型が違うと別の関数になる。型や
constの食い違いはdumpbinで確認できる - テンプレートは、定義ごとヘッダーに置く
- 今回は、MSVC 19.44(Visual Studio 2022付属)で確認した。エラー番号や文面は、コンパイラや版で変わりうる
この記事のポイント
- LNK2019は、リンクの段階で実体が見つからないときのエラー。コンパイルが通るのは、宣言があるから
- エラー文の「シンボル」「参照元の関数」「ヒント」の3点を読む。装飾名は
undnameで戻せる - 原因は7通り再現した。①定義がない ②ビルドに入っていない ③.libの指定漏れ ④型違い ⑤テンプレート ⑥staticメンバ ⑦仮想関数
- ①と②は文面が同じ。ソースを探し続ける前に、ビルド対象を確認する
LNK2019は、エラー文の見た目が強烈なので身構えてしまいますが、読む場所は決まっています。実際に壊してみると、「このエラーが出たら、この順で見る」という流れが、はっきり見えました。


