【C++】LNK2005「既に定義されています」とは?|ヘッダーに実体を書くと起きる多重定義を再現して、inline・extern・staticの違いを実測

【C++】LNK2005「既に定義されています」とは?|ヘッダーに実体を書くと起きる多重定義を再現して、inline・extern・staticの違いを実測 C++

エラー一覧を見ると、行番号が書いてありません。代わりに書いてあるのは、a.objとmain.objという、ソースコードではないファイル名です。「既に定義されています」と言われても、どこを開けばいいのか分からない。LNK2005は、そういう迷子になりやすいエラーです。

先に答えを言うと、犯人はたいてい、ヘッダーファイルに書いた「実体」です。同じヘッダーを2つのcppが読むと、実体が2つできてしまいます。この記事では、これを実際に起こして、直し方を比べました。直し方によっては、エラーは消えるのに、値が別々になるものもあります。

検証は、Visual Studio 2022付属のMSVC(19.44、x64)を、コマンドラインのclで動かして行いました。前に書いたLNK2019(未解決の外部シンボル)が「実体が足りない」エラーなのに対して、今回のLNK2005は「実体が多すぎる」エラーです。

LNK2005とは?

LNK2005とは、

・リンクのとき、同じ名前の関数や変数の実体が、2か所以上で見つかったときに出るエラー

です。必ずLNK1169(複数回定義されているシンボルが見つかりました)とセットで出ます。

ポイントは、C++のビルドが、cppファイルを1つずつ別々にコンパイルすることです。ヘッダーに実体を書くと、そのヘッダーを#includeしたcppの数だけ、同じ実体がコピーされます。リンカーは、それらを1つにまとめられないので、エラーにします。#pragma onceやインクルードガードは、1つのcppの中での二重読み込みしか防げません。cppどうしの重複は、防げないのです。

原因を6パターン再現する

①ヘッダーに、関数の実体を書いた

いちばん多い原因です。ヘッダー(util.h)に、関数の中身まで書いてしまいました。

▼util.h
#pragma once

int add(int a, int b)
{
    return a + b;
}
▼a.cpp
#include "util.h"

int fromA()
{
    return add(1, 2);
}
▼main.cpp
#include <cstdio>
#include "util.h"

int fromA();

int main()
{
    std::printf("fromA = %d, add = %d\n", fromA(), add(3, 4));
    return 0;
}
main.cpp
a.cpp
コードを生成中...
a.obj : error LNK2005: "int __cdecl add(int,int)" (?add@@YAHHH@Z) は既に main.obj で定義されています。
main.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

main.cppもa.cppもutil.hを読むので、addの実体が、main.objとa.objの両方にできました。エラー文の「既にmain.objで定義されています」は、「a.objのaddが、main.objにあるものと重なった」という意味です。2つのobjに共通するヘッダーを探せば、犯人が見つかります。

直すには、関数にinlineを付けます。inlineは、「同じ実体が複数のcppにあっても、1つにまとめてよい」という印です。

▼util.h(直した)
#pragma once

inline int add(int a, int b)
{
    return a + b;
}
fromA = 3, add = 7

②ヘッダーに、変数の実体を書いた

グローバル変数でも、同じことが起きます。

▼var.h
#pragma once

int g_count = 0;
▼a.cpp
#include "var.h"

void countUp()
{
    ++g_count;
}

int readA()
{
    return g_count;
}
▼main.cpp
#include <cstdio>
#include "var.h"

void countUp();
int readA();

int main()
{
    countUp();
    countUp();
    countUp();
    std::printf("main側 g_count = %d / a.cpp側 g_count = %d\n", g_count, readA());
    return 0;
}
main.cpp
a.cpp
コードを生成中...
a.obj : error LNK2005: "int g_count" (?g_count@@3HA) は既に main.obj で定義されています。
main.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

こちらも、同じ形のエラーになりました。変数の直し方は3通りあります。それぞれ実行して、g_countを3回増やしたあとの値を比べました。

【重要】staticで直すと、エラーは消えるが、値が別物になる

エラーが消えるから、とstaticを付ける直し方を、よく見かけます。

▼var.h(staticを付けた)
#pragma once

static int g_count = 0;   // それぞれのcppに、別々の変数ができる
main側 g_count = 0 / a.cpp側 g_count = 3

ビルドは通りましたが、実行結果が変です。a.cppの中で3回増やしたのに、main.cppから見ると、g_countは0のままでした。staticを付けると、変数が、cppファイルごとに別々に作られるからです。「共有したい変数」にstaticを付けると、エラーは消えても、コードの意味が変わります。

共有したいなら、次の2つの方法を使います。

▼var.h(宣言だけにした)
#pragma once

extern int g_count;   // 宣言だけ(実体はglobals.cppに1つだけ)
▼globals.cpp(実体は、ここだけ)
#include "var.h"

int g_count = 0;      // 実体はここだけ
main側 g_count = 3 / a.cpp側 g_count = 3

externは、「実体は、どこか別の場所にあります」という宣言です。実体を、1つのcppだけに書いて、ヘッダーには宣言だけを置くと、両方から同じ変数が見えて、3と3になりました。

C++17以降なら、もっと簡単な方法があります。inlineを、変数にも付けられるようになりました。

▼var.h(inline変数)
#pragma once

inline int g_count = 0;   // C++17: ヘッダーに書いても、実体は1つにまとめられる
main側 g_count = 3 / a.cpp側 g_count = 3

実体を書くファイルを増やさずに、3と3で共有できました。ただし、C++14以前では使えません。同じコードを/std:c++14でビルドすると、次のエラーになりました。

main.cpp
C:\blogtest\dup\c2_inline\var.h(3): error C7525: インライン変数には、最低でも '/std:c++17' が必要です
a.cpp
C:\blogtest\dup\c2_inline\var.h(3): error C7525: インライン変数には、最低でも '/std:c++17' が必要です
コードを生成中...

③クラスのメンバ関数を、ヘッダーのクラスの外に書いた

クラスの宣言は.h、メンバ関数の中身は.cpp、というルールを守ったつもりで、ヘッダーの中で、クラスの外に中身を書いてしまうパターンです。

▼player.h
#pragma once

class Player
{
public:
    int hp() const;
};

int Player::hp() const
{
    return 100;
}
main.cpp
a.cpp
コードを生成中...
a.obj : error LNK2005: "public: int __cdecl Player::hp(void)const " (?hp@Player@@QEBAHXZ) は既に main.obj で定義されています。
main.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

メンバ関数Player::hpが、重複しました。直し方は2通りで、どちらも実行できました。

▼player.h(クラスの中に書く)
#pragma once

class Player
{
public:
    int hp() const { return 100; }   // クラスの中に書くと、自動でinline扱いになる
};
fromA = 100, main = 100
▼player.h(クラスの外に書く場合は、inlineを付ける)
#pragma once

class Player
{
public:
    int hp() const;
};

inline int Player::hp() const   // クラスの外に書くなら、inlineを付ける
{
    return 100;
}
fromA = 100, main = 100

クラスの中に書いた関数は、自動でinline扱いになるので、何も付けなくてよいのです。

④cppファイルを、#includeしている

ヘッダーではなく、cppファイル自体を#includeしてしまうパターンです。cppを読み込むと、その中身が、そっくりコピーされます。

▼main.cpp
#include <cstdio>
#include "a.cpp"          // .cppをincludeしている

int main()
{
    std::printf("fromA = %d\n", fromA());
    return 0;
}
main.cpp
a.cpp
コードを生成中...
a.obj : error LNK2005: "int __cdecl fromA(void)" (?fromA@@YAHXZ) は既に main.obj で定義されています。
main.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

a.cppは、単独でもビルドされるので、fromAが2つできました。直すには、宣言だけの.hを作って、そちらを#includeします。

▼a.h
#pragma once

int fromA();
fromA = 42

⑤ランタイムライブラリの設定が、ライブラリと食い違っている

自分のコードに重複が無くても、LNK2005が出ることがあります。標準ライブラリ(CRT)の種類が、exeとlibで食い違っているときです。ランタイムライブラリには、/MT(静的リンク)と/MD(DLLを使う)があります。/MTで作ったlibを、/MDのexeからリンクしてみました。ライブラリ側はiostreamとstringを使っています。

▼crtlib.cpp(/MTでlibにした)
#include <iostream>
#include <string>

void hello()
{
    std::string s = "hello from lib";
    std::cout << s << std::endl;
}
▼main.cpp(/MDでビルド)
#include <iostream>

void hello();

int main()
{
    hello();
    std::cout << "main" << std::endl;
    return 0;
}
main.cpp
crtlib.lib(crtlib.obj) : error LNK2038: 'RuntimeLibrary' の不一致が検出されました。値 'MT_StaticRelease' が MD_DynamicRelease の値 'main.obj' と一致しません。
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: bool __cdecl std::ios_base::good(void)const " (?good@ios_base@std@@QEBA_NXZ) は既に crtlib.lib(crtlib.obj) で定義されています。
(中略: 同じ形のエラーが、ほかに51行続く)
LINK : warning LNK4098: defaultlib 'LIBCMT' は他のライブラリの使用と競合しています。/NODEFAULTLIB:library を使用してください。
main.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

LNK2005が24行、LNK2038(RuntimeLibraryの不一致が検出されました)が29行、合計50行以上のエラーが出ました。最初に出ているLNK2038の文面が、本当の原因を教えてくれています。「MT_StaticReleaseとMD_DynamicReleaseが一致しません」。直すには、libも/MDで作り直して、設定をそろえます。

hello from lib
main

Visual Studioでは、プロジェクトのプロパティの「C/C++ → コード生成 → ランタイムライブラリ」が、この設定です。exe側と、リンクするlib側で、同じ種類にそろえます。

なお、同じ/MTと/MDの食い違いでも、printfしか使わないlibでは、別の結果になりました。

main.cpp
LINK : warning LNK4098: defaultlib 'LIBCMT' は他のライブラリの使用と競合しています。/NODEFAULTLIB:library を使用してください。

今回の環境では、LNK4098の警告が出ただけで、リンクは通り、実行も成功しました(hello from libとmainが表示されました)。エラーにならないから安全、とは限らないので、警告が出たら、設定をそろえておくのが無難です。

⑥const変数は、重複しない

最後に、「ヘッダーに変数を書くと、必ず重複する」わけではない、という例です。constを付けたグローバル変数は、C++では、ファイルごとに独立した変数として扱われます(内部リンケージ)。

▼consts.h
#pragma once

const int kMax = 10;   // constのグローバル変数は、ファイルごとに内部リンケージ
fromA = 20, kMax = 10

エラーは出ず、問題なく動きました。定数をヘッダーに書くときは、const(またはconstexpr)を付けておけば、重複の心配はありません。

原因ごとの、直し方の早見表

書いてあったものエラー文の特徴直し方
ヘッダーの関数の実体int __cdecl add(int,int)のように、関数名が出るinlineを付ける、またはcppに移す
ヘッダーの変数の実体int g_countのように、変数名が出るextern+cppに実体、またはC++17のinline変数
クラス外のメンバ関数の実体public: int __cdecl Player::hp(void)constクラスの中に書く、またはinlineを付ける
#include "a.cpp"a.cppにある関数が、重複している宣言だけの.hを作って、そちらを読む
/MTと/MDの混在LNK2038やmsvcprt.libが大量に出るexeとlibで、ランタイムライブラリをそろえる
今回再現した範囲での目安。直し方は他にもあるので、まず疑う順番として使う

LNK2005のよくある失敗例と対処法

①インクルードガードがあるのに、なぜ?と悩む

#pragma onceは、1つのcppの中で、同じヘッダーを二重に読み込まないための仕組みです。cppは別々にコンパイルされるので、cppどうしの重複は防げません。

②staticを付けて、エラーを黙らせる

実験で見たとおり、共有したい変数にstaticを付けると、値が別々になります。エラーが消えたときは、「共有できているか」を、必ず確認します。

③エラー文の.objの名前から、犯人を探す

「a.objがmain.objと重なった」というエラー文は、両方のcppが、共通のヘッダーを読んでいることを示しています。2つのcppに共通する#includeから、探します。

注意点

  • LNK2005は、同じ実体が、2か所以上にあるときのエラー。LNK2019(実体が無い)の、逆の状態
  • インクルードガード(#pragma once)は、cppどうしの重複は防げない
  • ヘッダーには、原則として宣言だけを書く。実体を書くならinline(C++17なら、変数にもinline)
  • staticで直すと、値が別物になる(今回の実験では、3ではなく0が見えた)
  • constのグローバル変数は、ファイルごとに別のものとして扱われるので、重複しない
  • 大量のLNK2005は、ランタイムライブラリの不一致(LNK2038)が原因のことがある
  • 今回は、MSVC 19.44(Visual Studio 2022付属、/std:c++17)で確認した。エラー文の細部は、版で変わりうる

この記事の要点

  • LNK2005は、ヘッダーに実体を書いたことが、いちばん多い原因。エラー文の.objは、「共通のヘッダーを探せ」というヒント
  • 直し方は、関数ならinline、変数ならextern+1か所の実体か、C++17のinline変数
  • staticは、エラーを消すが、値を別物にする
  • 大量に出るときは、LNK2038の文面(/MTと/MDの不一致)を、先に読む

ヘッダーには宣言、cppには実体。この分担を守るだけで、LNK2005の大半は避けられます。そして、もし出てしまったときは、エラー文の.objの名前を2つ並べて、「両方が読んでいるヘッダーはどれか」と考えれば、探す範囲がぐっと狭まります。

あわせて読みたい