未定義の参照/未解決の外部シンボルエラーとは何ですか?どのように修正しますか?


1493

未定義の参照/未解決の外部シンボルエラーとは何ですか?一般的な原因は何ですか?それらを修正/防止する方法は?

自由に編集/追加してください。


@LuchianGrigore 「お気軽に回答を追加してください」許可する場合は、関連リンク(IMHO)を主要な回答に追加することをお勧めします。
πάνταῥεῖ

19
この質問はあまりにも一般的すぎて、有用な答えを認めることができません。1000以上の評判の人々がこれについてコメントしている数は、疑問を投げかけることなしには信じられません。それまでの間、私には理にかなっていて非常に役立つ多くの質問が閉じられています。
アルバートファンデルホルスト

10
@AlbertvanderHorst「この質問はあまりにも一般的すぎて、役に立つ答えを認めることができません。」以下の答えは役に立たないのですか?
LF

4
一般的すぎるとフラグが付けられた質問への多くの回答と同様に、それらは役に立つかもしれません。
アルバートファンデルホルスト

1
再現可能な最小限の例を、ほとんどの新規ユーザーに正直にお願いするものとして見たいと思います。私はそれによって何も意味しません、それはただです-私たちが自分たちに強制しないルールを人々が従うことを期待することはできません。
Danilo、

回答:


850

C ++プログラムのコンパイルは、2.2で 指定されているように、いくつかのステップで行われます(参照用にキーストンプソンへのクレジット)

翻訳の構文規則間の優先順位は、次のフェーズで指定されます[脚注を参照]

  1. 物理ソースファイルの文字は、必要に応じて、実装定義の方法で、基本的なソース文字セット(行末インジケーターの改行文字を導入)にマップされます。[をちょきちょきと切る]
  2. バックスラッシュ文字(\)の直後に改行文字が続くインスタンスはそれぞれ削除され、物理ソース行が結合されて論理ソース行が形成されます。[をちょきちょきと切る]
  3. ソースファイルは、前処理トークン(2.5)と一連の空白文字(コメントを含む)に分解されます。[をちょきちょきと切る]
  4. 前処理ディレクティブが実行され、マクロ呼び出しが展開され、_Pragma単項演算子式が実行されます。[をちょきちょきと切る]
  5. 文字リテラルまたは文字列リテラルの各ソース文字セットメンバー、および文字リテラルまたは非raw文字列リテラルの各エスケープシーケンスとユニバーサル文字名は、実行文字セットの対応するメンバーに変換されます。[をちょきちょきと切る]
  6. 隣接する文字列リテラルトークンが連結されます。
  7. トークンを区切る空白文字は重要ではなくなりました。各前処理トークンはトークンに変換されます。(2.7)。結果のトークンは、構文的および意味的に分析され、翻訳単位として翻訳されます。[をちょきちょきと切る]
  8. 翻訳された翻訳単位とインスタンス化単位は次のように組み合わされます:[SNIP]
  9. すべての外部エンティティ参照が解決されます。ライブラリコンポーネントは、現在の翻訳で定義されていないエンティティへの外部参照を満たすためにリンクされます。このようなトランスレータの出力はすべて、その実行環境での実行に必要な情報を含むプログラムイメージに収集されます。(強調鉱山)

[脚注]実際には異なるフェーズが一緒に折りたたまれることもありますが、実装はこれらの個別のフェーズが発生するかのように動作する必要があります。

指定されたエラーは、コンパイルのこの最後の段階で発生し、最も一般的にはリンクと呼ばれます。基本的には、一連の実装ファイルをオブジェクトファイルまたはライブラリにコンパイルし、それらを連携させる必要があることを意味します。

でシンボルを定義したとaa.cppます。今、そのシンボルをb.cpp 宣言して使用しました。リンクする前に、単にそのシンボルがどこかで定義されていると仮定しますが、まだどこでもかまいません。リンクフェーズでは、シンボルを見つけて、正しくb.cpp(実際には、それを使用するオブジェクトまたはライブラリに)リンクします。

Microsoft Visual Studioを使用している場合は、プロジェクトが.libファイルを生成することがわかります。これらには、エクスポートされたシンボルのテーブルとインポートされたシンボルのテーブルが含まれています。インポートされたシンボルは、リンク先のライブラリに対して解決され、エクスポートされたシンボルは、それを使用するライブラリ.lib(存在する場合)に提供されます。

他のコンパイラ/プラットフォームにも同様のメカニズムが存在します。

一般的なエラーメッセージがあるerror LNK2001error LNK1120error LNK2019のためのMicrosoftのVisual Studioundefined reference to にSymbolNameのためのGCC

コード:

struct X
{
   virtual void foo();
};
struct Y : X
{
   void foo() {}
};
struct A
{
   virtual ~A() = 0;
};
struct B: A
{
   virtual ~B(){}
};
extern int x;
void foo();
int main()
{
   x = 0;
   foo();
   Y y;
   B b;
}

GCCで次のエラーが生成されます。

/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status

およびMicrosoft Visual Studioでの同様のエラー:

1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

一般的な原因は次のとおりです。


16
個人的には、MSリンカーのエラーメッセージはGCCエラーと同じくらい読みやすいと思います。それらには、未解決の外部のマングル名とアンマングル名の両方を含めるという利点もあります。マングルされた名前は、ライブラリまたはオブジェクトファイルを直接見て問題が何であるかを確認する必要がある場合に役立ちます(たとえば、呼び出し規約の不一致)。また、ここでエラーが発生したMSVCのバージョンはわかりませんが、新しいバージョンには、未解決の外部シンボルを参照する関数の名前(マングルおよび非マングルの両方)が含まれています。
Michael Burr

5
デビッド・ドライスデールは、リンカーがどのように機能するかについて素晴らしい記事を書きました:リンカーの初心者向けガイド。この質問のトピックを考えると、役に立つかもしれないと思った。
プレサッコ2015年

@TankorSmash gccを使用しますか?より正確にはMinGW。
doug65536

1
@luchian上記のエラーを修正して正しく追加するとよいでしょう
Phalgun

178

クラスのメンバー:

純粋なvirtualデストラクタには実装が必要です。

デストラクターを純粋に宣言するには、それを定義する必要があります(通常の関数とは異なります)。

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

これは、オブジェクトが暗黙的に破棄されるときに基本クラスのデストラクタが呼び出されるため、定義が必要になるために発生します。

virtual メソッドは、実装するか、純粋として定義する必要があります。

これは非virtual定義のないメソッドにいますが、純粋な宣言がダミーのvtableを生成し、関数を使用せずにリンカーエラーが発生する可能性があるという理由が追加されています。

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

これが機能するためには、宣言します X::foo()純粋としてします。

struct X
{
    virtual void foo() = 0;
};

virtualクラスメンバ

一部のメンバーは、明示的に使用されていなくても定義する必要があります。

struct A
{ 
    ~A();
};

次の場合、エラーが発生します。

A a;      //destructor undefined

実装は、クラス定義自体でインラインにすることができます。

struct A
{ 
    ~A() {}
};

または外:

A::~A() {}

実装がクラス定義の外側であるがヘッダー内にある場合、メソッドはinline複数の定義を防ぐためにマークする必要があります。

使用する場合、使用するすべてのメンバーメソッドを定義する必要があります。

よくある間違いは、名前の修飾を忘れることです。

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

定義は

void A::foo() {}

staticデータメンバーは、クラスの外部の単一の翻訳単位で定義する必要があります。

struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

初期化子はstatic const、クラス定義内の整数型または列挙型のデータメンバーに提供できます。ただし、このメンバーのodrの使用には、上記の名前空間スコープの定義が必要です。C ++ 11では、すべてのstatic constデータメンバーのクラス内の初期化が可能です。


両方を行うことは可能であり、dtorは実際には例外ではないことを強調したいと思うかもしれません。(一見したところ、それは一見して明らかではありません。)
Deduplicator

112

適切なライブラリ/オブジェクトファイルへのリンクまたは実装ファイルのコンパイルの失敗

通常、各変換単位は、その変換単位で定義されたシンボルの定義を含むオブジェクトファイルを生成します。これらのシンボルを使用するには、それらのオブジェクトファイルに対してリンクする必要があります。

gccの下では、コマンドラインでリンクされるすべてのオブジェクトファイルを指定するか、実装ファイルを一緒にコンパイルします。

g++ -o test objectFile1.o objectFile2.o -lLibraryName

libraryNameここでは、プラットフォーム固有の追加なしに、ライブラリーのちょうど裸の名前です。たとえば、Linuxでは通常、ライブラリファイルが呼び出されますが、libfoo.so書き込むだけ-lfooです。Windowsでは同じファイルが呼び出される可能性がありますがfoo.lib、同じ引数を使用します。これらのファイルを見つけることができるディレクトリを、を使用して追加する必要がある場合があります-L‹directory›-lまたはの後にスペースを書かないようにしてください-L

XCodeの:>ドラッグとプロジェクトフォルダに実際のライブラリの参照をドロップ- -ライブラリ検索パスを追加>ヘッダ検索パスのユーザーを追加します。

下でMSVS、プロジェクトに追加されたファイルは、自動的にそのオブジェクトファイルは、一緒にリンクしているとlib、ファイルは(一般的な使い方で)生成されます。別のプロジェクトでシンボルを使用するにはlib、プロジェクト設定にファイルを含める必要があります。これは、プロジェクトプロパティの[リンカー]セクションで行いますInput -> Additional Dependencies。(libファイルへのパスはに追加する必要がありますLinker -> General -> Additional Library Directories)とともに提供されるサードパーティライブラリを使用する場合libファイル、そうしないと、通常、エラーが発生します。

また、ファイルをコンパイルに追加するのを忘れている場合もあります。その場合、オブジェクトファイルは生成されません。gccでは、ファイルをコマンドラインに追加します。でMSVSは、プロジェクトにファイルを追加し、それは(ファイルはいえ、手動で、個別にビルドから除外することができます)、それを自動的にコンパイルようになります。

Windowsプログラミングでは、必要なライブラリをリンクしなかったことを示す兆候は、未解決のシンボルの名前がで始まること__imp_です。ドキュメントで関数の名前を検索すると、使用する必要があるライブラリが示されているはずです。たとえば、MSDNは「ライブラリ」というセクションの各関数の下部にあるボックスに情報を配置します。


1
(初心者はしばしば、プロジェクトが大きくなって.oファイルを構築する前に行う)gcc main.cではなく、一般的な間違いを明示的にカバーできればよいでしょうgcc main.c other.c
MM

101

変数または関数を宣言しましたが、定義しませんでした。

典型的な変数宣言は

extern int x;

これは単なる宣言なので、1つの定義が必要です。対応する定義は次のようになります。

int x;

たとえば、次の場合はエラーが発生します。

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

同様の注意が関数にも当てはまります。関数を定義せずに宣言すると、エラーが発生します。

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

実装する関数が宣言した関数と完全に一致するように注意してください。たとえば、cv-qualifiersが一致していない場合があります。

void foo(int& x);
int main()
{
   int x;
   foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
                          //for void foo(int& x)

不一致の他の例には、

  • ある名前空間で宣言され、別の名前空間で定義された関数/変数。
  • クラスメンバーとして宣言された関数/変数、グローバルとして定義(またはその逆)
  • 関数の戻り値の型、パラメーター番号と型、および呼び出し規約は、すべてが完全に一致しているわけではありません。

コンパイラからのエラーメッセージは、宣言されたが定義されていない変数または関数の完全な宣言を提供することがよくあります。指定した定義とよく比較してください。すべての詳細が一致していることを確認してください。


VSでは、ソースディレクトリに追加されて#includesいないヘッダーのファイルと一致するcppファイルも、不足している定義のカテゴリに分類されます。
Laurie Stearn 2018年

86

相互に依存するリンクライブラリが指定される順序が間違っています。

ライブラリが相互に依存している場合、ライブラリがリンクされる順序は重要です。ライブラリがあれば一般的には、Aライブラリに依存Bし、libA しなければならないの前に表示されますlibBリンカのフラグに。

例えば:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

ライブラリを作成します。

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

コンパイル:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out

だから、もう一度繰り返すため、注文はDOESの問題を!


奇妙なことに、私の場合、共有ライブラリに依存するオブジェクトファイルが1つありました。私はMakefileを変更し、ライブラリを入れていたAFTERのDebian上のgcc 4.8.4を持つオブジェクト。Centos 6.5とgcc 4.4では、Makefileは問題なく動作しました。
Marco Sulla

74

「未定義の参照/未解決の外部シンボル」とは

「未定義の参照/未解決の外部シンボル」とは何かを説明しようと思います。

注:私はg ++とLinuxを使用しており、すべての例はそのためです

たとえば、いくつかのコードがあります

// src1.cpp
void print();

static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;

int main()
{
    print();
    return 0;
}

そして

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
//extern int local_var_name;

void print ()
{
    // printf("%d%d\n", global_var_name, local_var_name);
    printf("%d\n", global_var_name);
}

オブジェクトファイルを作成する

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

アセンブラフェーズの後、エクスポートするシンボルを含むオブジェクトファイルがあります。シンボルを見てください

$ readelf --symbols src1.o
  Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL14local_var_name # [1]
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_var_name     # [2]

一部の行は関係ないため、出力から拒否しました

したがって、エクスポートするシンボルは次のようになります。

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cppは何もエクスポートせず、そのシンボルも確認していません

オブジェクトファイルをリンクする

$ g++ src1.o src2.o -o prog

それを実行します

$ ./prog
123

リンカーはエクスポートされたシンボルを確認してリンクします。ここで、src2.cppの行のコメントを解除してみます。

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
extern int local_var_name;

void print ()
{
    printf("%d%d\n", global_var_name, local_var_name);
}

オブジェクトファイルを再構築します

$ g++ -c src2.cpp -o src2.o

OK(エラーなし)、オブジェクトファイルのみをビルドするため、リンクはまだ行われていません。リンクしてみてください

$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status

これは、local_var_nameが静的である、つまり他のモジュールからは見えないために発生しました。今より深く。翻訳フェーズの出力を取得する

$ g++ -S src1.cpp -o src1.s

// src1.s
look src1.s

    .file   "src1.cpp"
    .local  _ZL14local_var_name
    .comm   _ZL14local_var_name,4,4
    .globl  global_var_name
    .data
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; assembler code, not interesting for us
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

したがって、local_var_nameのラベルがないことがわかりました。そのため、リンカーはそれを見つけませんでした。しかし、私たちはハッカーです:)そしてそれを修正することができます。テキストエディターでsrc1.sを開き、変更します。

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

つまり、以下のようにする必要があります

    .file   "src1.cpp"
    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789
    .globl  global_var_name
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; ...

local_var_nameの可視性を変更し、その値を456789に設定しました。そこからオブジェクトファイルを作成してみてください

$ g++ -c src1.s -o src2.o

わかりました、readelfの出力(シンボル)を参照してください

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

現在、local_var_nameはBind GLOBAL(LOCALでした)を持っています

リンク

$ g++ src1.o src2.o -o prog

それを実行します

$ ./prog 
123456789

はい、ハックします:)

したがって、結果として、リンカーがオブジェクトファイルでグローバルシンボルを見つけられない場合、「未定義の参照/未解決の外部シンボルエラー」が発生します。


71

シンボルはCプログラムで定義され、C ++コードで使用されました。

関数(または変数)void foo()がCプログラムで定義されており、C ++プログラムでそれを使用しようとしました:

void foo();
int main()
{
    foo();
}

C ++リンカーは名前がマングルされることを想定しているため、関数を次のように宣言する必要があります。

extern "C" void foo();
int main()
{
    foo();
}

同様に、Cプログラムで定義する代わりに、関数(または変数)void foo()をC ++で定義しましたが、Cリンケージを使用していました。

extern "C" void foo();

そして、C ++リンケージのあるC ++プログラムでそれを使用しようとします。

ライブラリ全体がヘッダーファイルに含まれている(そしてCコードとしてコンパイルされている)場合 インクルードは次のようにする必要があります。

extern "C" {
    #include "cheader.h"
}

5
それとも、Cライブラリを開発する場合は逆に、素敵なルールが持つすべてのエクスポートされた宣言を囲むことにより、ヘッダファイル(複数可)を保護することである#ifdef __cplusplus [\n] extern"C" { [\n] #endif#ifdef __cplusplus [\n] } [\n] #endif[\n]本当のキャリッジ・リターンであることが、私はコメントでこれを適切に書き込むことはできません)。
Bentoy13、2015

上記のコメントのように、「作成言語が混在したヘッダ」のセクションでは、ここで助け:oracle.com/technetwork/articles/servers-storage-dev/...
zanbri

本当に助けてくれました!これは私がこの質問を探したときの私のケースでした
knightofhorizo​​n

これは、通常のC ++ヘッダーファイルを誤ってextern C:で囲まれた場合にも発生しますextern "C" { #include <myCppHeader.h> }
ComFreek

68

他のすべてが失敗した場合は、再コンパイルします。

最近、問題のあるファイルを再コンパイルするだけで、Visual Studio 2012で未解決の外部エラーを取り除くことができました。私が再構築したとき、エラーはなくなりました。

これは通常、2つ(またはそれ以上)のライブラリに循環依存関係がある場合に発生します。ライブラリAはB.libのシンボルを使用しようとし、ライブラリBはA.libのシンボルを使用しようとします。最初はどちらも存在しません。Aをコンパイルしようとすると、B.libが見つからないため、リンク手順が失敗します。A.libは生成されますが、dllは生成されません。次に、Bをコンパイルします。これは成功し、B.libを生成します。B.libが見つかったので、Aの再コンパイルは機能します。


1
正解-これは、ライブラリに循環依存関係がある場合に発生します。
Luchian Grigore

1
私はあなたの答えを拡張し、メインの答えにリンクしました。ありがとう。
Luchian Grigore

57

モジュール/ dll(コンパイラ固有)間でメソッド/クラスを誤ってインポート/エクスポートします。

MSVSでは、およびを使用して__declspec(dllexport)、エクスポートおよびインポートするシンボルを指定する必要があります__declspec(dllimport)

この2つの機能は通常、マクロを使用して取得されます。

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

マクロTHIS_MODULEは、関数をエクスポートするモジュールでのみ定義されます。そのように、宣言は:

DLLIMPEXP void foo();

に拡大する

__declspec(dllexport) void foo();

現在のモジュールにはその定義が含まれているため、関数にエクスポートするようコンパイラーに指示します。別のモジュールに宣言を含めると、次のように展開されます

__declspec(dllimport) void foo();

そして、コンパイラーに、定義がリンク先のライブラリーの1つにあることを伝えます(また、 1)

同様に、クラスをインポート/エクスポートできます。

class DLLIMPEXP X
{
};

2
完全にするために、この回答ではGCC visibilityとWindowsの.defファイルについて言及する必要があります。これらもシンボル名と存在に影響を与えるためです。
rubenvb

@rubenvb私は.def古くからファイルを使用していません。回答を追加するか、これを編集してください。
Luchian Grigore

57

これは、すべてのVC ++プログラマーが何度も何度も目にした最もわかりにくいエラーメッセージの1つです。最初に物事を明確にしましょう。

A.シンボルとは? つまり、シンボルは名前です。変数名、関数名、クラス名、typedef名、またはC ++言語に属する名前と記号を除くすべての名前を使用できます。これは、依存ライブラリ(別のユーザー定義)によってユーザー定義または導入されます。

B.外部とは何ですか? VC ++では、すべてのソースファイル(.cpp、.cなど)が翻訳単位と見なされ、コンパイラは一度に1つの単位をコンパイルし、現在の翻訳単位に対して1つのオブジェクトファイル(.obj)を生成します。(このソースファイルに含まれるすべてのヘッダーファイルは前処理され、この翻訳単位の一部と見なされることに注意してください)翻訳単位内のすべてが内部と見なされ、それ以外はすべて外部と見なされます。C ++では、などのキーワードを使用して外部シンボルを参照できますextern__declspec (dllimport)というように。

C.「解決」とは何ですか? 解決はリンク時の用語です。リンク時、リンカーは、内部で定義を見つけることができないオブジェクトファイル内のすべてのシンボルの外部定義を見つけようとします。この検索プロセスの範囲:

  • コンパイル時に生成されたすべてのオブジェクトファイル
  • このビルドアプリケーションの追加の依存関係として明示的または暗黙的に指定されているすべてのライブラリ(.lib)。

この検索プロセスは、解決と呼ばれます。

D.最後に、なぜ未解決の外部シンボルですか? リンカは、内部に定義がないシンボルの外部定義を見つけることができない場合、未解決の外部シンボルエラーを報告します。

E. LNK2019の考えられる原因:未解決の外部シンボルエラー。このエラーは、リンカが外部シンボルの定義を見つけられなかったことが原因であることがすでにわかっています。考えられる原因は次のように分類できます。

  1. 定義が存在します

たとえば、a.cppで定義されたfooという関数がある場合:

int foo()
{
    return 0;
}

b.cppでは、関数fooを呼び出したいので、

void foo();

関数foo()を宣言し、それを別の関数本体で呼び出すには、次のようにしますbar()

void bar()
{
    foo();
}

このコードをビルドすると、fooが未解決のシンボルであるというLNK2019エラーが表示されます。この場合、foo()の定義はa.cppにありますが、呼び出しているものとは異なります(異なる戻り値)。これは定義が存在する場合です。

  1. 定義が存在しません

ライブラリの一部の関数を呼び出したいが、インポートライブラリがProject | Properties | Configuration Properties | Linker | Input | Additional Dependencyプロジェクト設定の追加の依存関係リスト(から設定)に追加されていない場合。現在、定義は現在の検索範囲に存在しないため、リンカーはLNK2019を報告します。


1
体系的な説明ありがとうございます。これを読んだ後、ここで他の答えを理解することができました。
displayName

55

テンプレートの実装は表示されません。

特殊化されていないテンプレートの定義は、それらを使用するすべての翻訳単位に表示される必要があります。つまり、テンプレートの定義を実装ファイルに分離することはできません。実装を分離する必要がある場合、通常の回避策はimpl、テンプレートを宣言するヘッダーの最後に含めるファイルを用意することです。一般的な状況は次のとおりです。

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

これを修正するには、次の定義を移動する必要があります X::fooヘッダーファイルまたはそれを使用する翻訳単位から見える場所にます。

特殊化されたテンプレートは実装ファイルに実装でき、実装は可視である必要はありませんが、特殊化は事前に宣言する必要があります。

詳細な説明と考えられる別の解決策(明示的なインスタンス化)については、この質問と回答を参照してください。


1
「テンプレートはヘッダーで定義する必要がある」の詳細については、stackoverflow.com
questions / 495021

41

未定義の参照WinMain@16または同様の「異常な」 main()エントリポイント参照(特に)。

実際のIDEで適切なプロジェクトタイプを選択し忘れている可能性があります。IDEは、一般的に使用されるint main(int argc, char** argv);シグネチャの代わりに、たとえばWindowsアプリケーションプロジェクトをそのようなエントリポイント関数(上記の欠落しているリファレンスで指定されている)にバインドする場合があります。

IDEがプレーンコンソールプロジェクトをサポートしている場合は、Windowsアプリケーションプロジェクトではなく、このプロジェクトタイプを選択できます。


以下は、実世界の問題からより詳細に処理されたcase1case2です。


2
この質問を指摘せざるを得ません。これは、主な機能がないことよりも、主な機能がないことが原因であることが多いという事実を指摘しWinMainます。有効なC ++プログラムにはが必要mainです。
クリス2014年

36

また、サードパーティのライブラリを使用している場合は、正しい32/64ビットのバイナリがあることを確認してください


34

Microsoftは、#pragmaリンク時に正しいライブラリを参照するように提供しています。

#pragma comment(lib, "libname.lib")

ライブラリのディレクトリを含むライブラリパスに加えて、これはライブラリの完全な名前にする必要があります。


34

Visual Studio NuGetパッケージを新しいツールセットバージョンに更新する必要がある

libpngをVisual Studio 2013にリンクしようとしてこの問題が発生しました。問題は、パッケージファイルにVisual Studio 2010および2012のライブラリしかなかったことです。

正しい解決策は、開発者が更新されたパッケージをリリースしてからアップグレードすることを期待することですが、VS2013の追加設定をハッキングしてVS2012ライブラリファイルを指定することでうまくいきました。

パッケージを(packagesソリューションのディレクトリ内のフォルダーにある)パッケージを編集し、packagename\build\native\packagename.targetsそのファイルを見つけて、すべてのv110セクションをコピーしました。条件フィールドのv110をに変更しv120ましが、ファイル名のパスをすべてそのままにするように非常に注意していますv110。これにより、Visual Studio 2013が2012年のライブラリにリンクできるようになり、この場合は機能しました。


1
これは具体的すぎるようです-おそらく新しいスレッドがこの答えのためのより良い場所でしょう。
Luchian Grigore、2015年

3
@LuchianGrigore:その質問はまさにこの問題だったので、私はここに投稿したかったのですが、この質問の重複としてマークされていたので、そこで答えることはできませんでした。だから私は代わりにここに私の答えを投稿しました。
Malvineous、2015年

その質問はすでに受け入れられた答えを持っています。一般的な原因が上記にリストされているため、重複としてマークされています。含まれていないライブラリーのすべての問題についてここで答えがあればどうなるでしょうか?
Luchian Grigore

6
@LuchianGrigore:この問題はライブラリに固有のものではなく、Visual Studioのパッケージ管理システムを使用するすべてのライブラリに影響します。私たちはたまたま私たち2人でlibpngに問題があったため、たまたま他の質問を見つけました。libxml2、libiconv、glewでも同じ問題が(同じ解決策で)発生しました。その質問はVisual Studioのパッケージ管理システムの問題に関するものであり、私の回答は理由を説明し、回避策を提供します。誰かが「未解決の外部」を見て、それが実際にはパッケージ管理の問題であるときに、それを標準のリンカーの問題であると想定しました。
Malvineous、2015年

34

数千の.cppファイルと数千の.hファイルを含むc ++で記述された大きなプロジェクトがあるとします。また、プロジェクトが10個の静的ライブラリに依存しているとしましょう。Windowsを使用していて、Visual Studio 20xxでプロジェクトをビルドするとします。Ctrl + F7 Visual Studioを押してソリューション全体のコンパイルを開始すると(ソリューションにプロジェクトが1つしかないと仮定)

コンパイルの意味は何ですか?

  • Visual Studioはファイル.vcxprojを検索し、拡張子.cppを持つ各ファイルのコンパイルを開始します。コンパイルの順序は定義されていないので、ファイルmain.cppが最初にコンパイルされると想定しないでください
  • .cppファイルがファイル.cppで定義されているかどうかわからないシンボルを見つけるために、追加の.hファイルに依存している場合
  • コンパイラーが1つのシンボルを見つけられなかった1つの.cppファイルが存在する場合、コンパイラーの時間エラーにより、メッセージシンボルxが見つかりませんでした
  • 拡張子が.cppのファイルごとにオブジェクトファイル.oが生成され、Visual StudioはProjectName.Cpp.Clean.txtという名前のファイルに出力を書き込みますます。このファイルには、リンカーで処理する必要のあるすべてのオブジェクトファイルが含まれています。

コンパイルの2番目のステップは、リンカーによって行われます。リンカーは、すべてのオブジェクトファイルをマージし、最終的に出力(実行可能ファイルまたはライブラリである可能性があります)をビルドする必要があります

プロジェクトをリンクする手順

  • すべてのオブジェクトファイルを解析し、ヘッダーでのみ宣言された定義を見つけます(例:前の回答で述べたクラスの1つのメソッドのコード、またはクラス内のメンバーである静的変数の初期化イベント)
  • オブジェクトファイルで1つのシンボルが見つからなかった場合は、追加ライブラリでも検索されます。プロジェクトに新しいライブラリを追加するには、構成プロパティ -> VC ++ディレクトリ -> ライブラリディレクトリで、ライブラリと構成プロパティを検索するための追加フォルダーを指定します -> リンカー -> ライブラリの名前を指定するための入力。-リンカが1つの.cppで記述したシンボルを見つけられなかった場合、リンカの時間エラーが発生しますerror LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

観察

  1. リンカが1つのシンボルを見つけると、他のライブラリでそのシンボルを検索しません
  2. ライブラリをリンクする順序は重要です。
  3. リンカが1つの静的ライブラリで外部シンボルを見つけた場合、プロジェクトの出力にシンボルを含めますが、ライブラリが共有(動的)の場合、出力にコード(シンボル)を含めませんが、 ランタイムクラッシュが発生する可能性があります発生する

この種のエラーを解決する方法

コンパイラー時間エラー:

  • C ++プロジェクトの構文が正しいことを確認してください。

リンカー時間エラー

  • ヘッダーファイルで宣言するすべてのシンボルを定義する
  • #pragma onceコンパイルされている現在の.cppにすでにヘッダーが含まれている場合に、コンパイラーがヘッダーを1つも含まないようにするために使用します。
  • 外部ライブラリに、ヘッダーファイルで定義した他のシンボルと競合する可能性のあるシンボルが含まれていないことを確認してください
  • テンプレートを使用して、ヘッダーファイルに各テンプレート関数の定義が含まれていることを確認します。これにより、コンパイラーはインスタンス化に適切なコードを生成できます。

あなたの答えはビジュアルスタジオに固有のものではありませんか?この質問ではIDE /コンパイラツールを指定していないため、非ビジュアルスタジオパーツでは回答が役に立たなくなります。
Victor Polevoy

あなたが正しい 。しかし、コンパイル/リンクのすべてのIDEプロセスでは、少しずつ異なる方法で処理が行われます。ただし、ファイルはまったく同じように処理されます(フラグを解析するときにg ++でも同じことを行います。)

問題は実際にはIDEに関するものではなく、問題をリンクするための回答に関するものです。リンクの問題は、IDEではなく、コンパイラとビルドプロセスに関連しています。
Victor Polevoy

はい。ただし、ビルド/リンクプロセスは、g ++ / Visual Studio(VS for Microsoftが提供するコンパイラ)/ Eclipse / Net Beansで同じ方法で行われます

29

コンパイラ/ IDEのバグ

最近この問題があり、それはVisual Studio Express 2013のバグであることがわかりました。バグを克服するために、プロジェクトからソースファイルを削除して再度追加する必要がありました。

コンパイラ/ IDEのバグであると思われる場合に試す手順:

  • プロジェクトをクリーンアップします(一部のIDEにはこれを実行するオプションがあります。オブジェクトファイルを削除して手動で実行することもできます)
  • 元のプロジェクトからすべてのソースコードをコピーして、新しいプロジェクトを開始してください。

5
ツールが壊れていると信じることは、おそらく本当の原因からあなたをそらすでしょう。コンパイラーが問題を引き起こすよりも、間違いを犯した可能性が非常に高いです。ソリューションをクリーンアップするか、ビルド構成を再作成すると、ビルドエラーが修正される場合がありますが、これはコンパイラにバグがあることを意味するものではありません。リンクされた「バグであることが判明」はマイクロソフトによって確認されておらず、再現性もありません。
JDiMatteo 2016

4
@JDiMatteoこの質問には21の回答があるため、かなりの量の回答が「ありそうな」解決策になるとは限りません。尤度のしきい値を下回るすべての回答を却下すると、一般的なケースのほとんどがとにかく簡単に見つけられるため、このページは事実上役に立たなくなります。
developerbmw

27

リンカーを使用してエラーの診断を支援する

最近のほとんどのリンカーには、さまざまな程度で出力する詳細オプションが含まれています。

  • リンクの呼び出し(コマンドライン)、
  • リンクステージに含まれるライブラリに関するデータ
  • ライブラリの場所、
  • 使用された検索パス。

gccおよびclangの場合。あなたは、一般的に追加し-v -Wl,--verboseたり-v -Wl,-v、コマンドラインに。詳細については、こちらをご覧ください。

MSVCの場合、/VERBOSE(特に/VERBOSE:LIB)がリンクコマンドラインに追加されます。


26

リンクされた.libファイルは.dllに関連付けられています

同じ問題がありました。MyProjectとTestProjectというプロジェクトがあるとします。MyProjectのlibファイルをTestProjectに効果的にリンクしました。ただし、このlibファイルは、MyProjectのDLLがビルドされたときに作成されました。また、MyProjectのすべてのメソッドのソースコードは含まれていませんが、DLLのエントリポイントへのアクセスのみが含まれています。

この問題を解決するために、MyProjectをLIBとしてビルドし、TestProjectをこの.libファイルにリンクしました(生成された.libファイルをTestProjectフォルダーにコピーアンドペーストします)。その後、MyProjectをDLLとして再度ビルドできます。TestProjectがリンクされているlibにはMyProjectのクラスのすべてのメソッドのコードが含まれているため、コンパイルされます。


25

リンカエラーに関しては、この質問に人々が向けられるようですので、ここに追加します。

GCC 5.2.0でのリンカーエラーの1つの考えられる理由は、新しいlibstdc ++ライブラリABIがデフォルトで選択されるようになったことです。

std :: __ cxx11名前空間またはタグ[abi:cxx11]の型を含むシンボルへの未定義の参照に関するリンカーエラーが発生する場合は、_GLIBCXX_USE_CXX11_ABIの異なる値でコンパイルされたオブジェクトファイルをリンクしようとしていることを示している可能性があります大きい。これは通常、古いバージョンのGCCでコンパイルされたサードパーティライブラリにリンクするときに発生します。サードパーティライブラリを新しいABIで再構築できない場合は、古いABIでコードを再コンパイルする必要があります。

したがって、5.1.0以降にGCCに切り替えるときに突然リンカーエラーが発生する場合は、これを確認する必要があります。


20

リンカースクリプトをサポートしないGNU ldのラッパー

一部の.soファイルは実際にはGNU ldリンカースクリプトです。たとえば、libtbb.soファイルは次の内容のASCIIテキストファイルです。

INPUT (libtbb.so.2)

より複雑なビルドの中には、これをサポートしていないものもあります。たとえば、コンパイラオプションに-vを含めると、mainwin gccラッパーmwdipがリンクするライブラリの詳細出力リストにあるリンカースクリプトコマンドファイルを破棄することがわかります。簡単な回避策は、リンカースクリプト入力コマンドを置き換えることです。代わりにファイルのコピー(またはシンボリックリンク)を含むファイル、たとえば

cp libtbb.so.2 libtbb.so

それとも、のではなく、例えば、.soというのフルパスで-l引数を置き換えることができ-ltbbDO/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


20

リンケージは、ライブラリを参照するオブジェクトファイルの前にライブラリを消費します

  • プログラムをコンパイルしてGCCツールチェーンにリンクしようとしています。
  • リンケージは、必要なすべてのライブラリとライブラリ検索パスを指定します
  • libfoo依存している場合libbar、リンケージはのlibfoo前に正しく置かれますlibbar
  • あなたの連結はして失敗しundefined reference to 、何かのエラー。
  • ただし、未定義の何かはすべて、ヘッダーファイルで宣言されて #includeおり、実際には、リンクしているライブラリで定義されています。

例はCにあります。C++も同様に可能です。

自分で作成した静的ライブラリを含む最小限の例

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

静的ライブラリを構築します。

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

プログラムをコンパイルします。

$ gcc -I. -c -o eg1.o eg1.c

あなたはそれをリンクしてlibmy_lib.a失敗しようとします:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

次のように、コンパイルとリンクを1つのステップで行った場合も同じ結果になります。

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

共有システムライブラリ、圧縮ライブラリを含む最小限の例 libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

プログラムをコンパイルします。

$ gcc -c -o eg2.o eg2.c

プログラムをリンクしlibzて失敗します:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

一度にコンパイルしてリンクする場合も同じです。

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

例2のバリエーションはpkg-config次のとおりです。

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

何が悪いの?

プログラムを作成するためにリンクするオブジェクトファイルとライブラリのシーケンスでは、それらを参照するオブジェクトファイルの前にライブラリを配置します。後にライブラリを配置する必要がありますを参照するオブジェクトファイルのます。

例1を正しくリンクします。

$ gcc -o eg1 eg1.o -L. -lmy_lib

成功:

$ ./eg1 
Hello World

例2を正しくリンクします。

$ gcc -o eg2 eg2.o -lz

成功:

$ ./eg2 
1.2.8

例2のpkg-configバリエーションを正しくリンクします。

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

説明

これ以降の読み取りはオプションです

デフォルトでは、ディストリビューションでGCCによって生成されたリンケージコマンドは、リンケージ内のファイルをコマンドラインシーケンスで左から右に消費します。ファイルが何かを参照していることが判明した場合 を参照していて、その定義が含まれていない場合、toはさらに右側のファイル内の定義を検索します。最終的に定義が見つかった場合、参照は解決されます。最後に参照が未解決のままである場合、リンケージは失敗します。リンカーは後方検索を行いません。

最初に、静的ライブラリを使用した例1my_lib.a

静的ライブラリは、オブジェクトファイルのインデックス付きアーカイブです。リンカが-lmy_libリンケージシーケンスで見つけ、これが静的ライブラリを参照している./libmy_lib.aことを理解した場合、リンカはプログラムにのオブジェクトファイルが必要かどうかを知りたいと考えますlibmy_lib.a

libmy_lib.aつまりmy_lib.o、にはオブジェクトファイルだけがあり、で定義されているのmy_lib.oは関数だけhwです。

リンカは、プログラムがmy_lib.oプログラムに参照していることをすでに知っhwていて、プログラムにすでに追加されている1つまたは複数のオブジェクトファイルで、プログラムがすでに必要であると判断し、追加済みのオブジェクトファイルにの定義hw

これが当てはまる場合、リンカーはmy_lib.oライブラリからのコピーを抽出し、プログラムに追加します。次に、プログラムにはの定義が含まれているためhw、への参照hw解決されます。

次のようにプログラムをリンクしようとすると:

$ gcc -o eg1 -L. -lmy_lib eg1.o

リンカ 、見たときにeg1.o プログラム追加ていません-lmy_lib。現時点では見ていませんのでeg1.o。プログラムはまだ何も参照しhwていません。作成するすべての参照が含まれているため、プログラムはまだまったく参照していませんeg1.o

そのため、リンカはmy_lib.oプログラムに追加せず、それ以上を使用しませんlibmy_lib.a

次に、を見つけてeg1.o、プログラムに追加します。リンケージシーケンスのオブジェクトファイルは、常にプログラムに追加されます。これで、プログラムはへの参照を作成しhw、の定義を含みませんhw。ただし、リンケージシーケンスには、定義が不足している可能性のあるものは何も残っていません。参照するhwまで終了し、未解決、および結合は失敗します。

2番目、例2、共有ライブラリlibz

共有ライブラリは、オブジェクトファイルなどのアーカイブではありません。これは、関数を持たず、代わりにそれが定義する他の複数のシンボルを公開するプログラムに非常によく似ているmainため、他のプログラムは実行時にそれらを使用できます。

多くのLinuxディストリビューションは本日、言語ドライバ(ように彼らのGCCツールチェーンを構成しgccg++gfortran(システムリンカに指示など)ldリンクに)上の共有ライブラリなど、必要に応じて基礎。それらのディストリビューションの1つを入手しました。

これは、リンカが-lzリンケージシーケンスで見つかり、これが共有ライブラリ(たとえば)を/usr/lib/x86_64-linux-gnu/libz.so参照していることが判明した場合、プログラムに追加されている未定義の参照に、次のような定義があるかどうかを知りたいという意味ですによってエクスポートされたlibz

これが真の場合、リンカーはチャンクをコピーしてプログラムに追加しませんlibz。代わりに、それはあなたのプログラムのコードを単にドクターします:

  • 実行時、システムプログラムローダーはlibz、プログラムのコピーを読み込むたびに、プログラムと同じプロセスにのコピーを読み込んで実行します。

  • 実行時に、プログラムがで定義されているものを参照する場合は常に libz、その参照はlibz同じプロセスののコピーによってエクスポートされた定義を使用します。

プログラムは、によってエクスポートされた定義を持つ1つのものlibz、つまりでzlibVersion一度だけ参照される関数を参照する必要がありeg2.cます。リンカがその参照をプログラムに追加し、によってエクスポートされた定義を見つけたlibz場合、参照は解決されます

しかし、次のようにプログラムをリンクしようとすると、

gcc -o eg2 -lz eg2.o

イベントの順序は、リンカー発見時点で、実施例1と同様に、ちょうど同じように間違っている-lz、存在しない全く彼らはで全てです:プログラムで何かへの参照eg2.o、まだ見ていません。そのため、リンカはを使用しないと判断しますlibz。に到達しeg2.o、それをプログラムに追加した後、への未定義の参照があるzlibVersionと、リンケージシーケンスは終了します。その参照は解決されず、リンケージは失敗します。

最後に、pkg-config例2 のバリエーションには今や明白な説明があります。シェル展開後:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

になる:

gcc -o eg2 -lz eg2.o

これもまた例2です。

例1では問題を再現できますが、例2では再現できません

リンケージ:

gcc -o eg2 -lz eg2.o

うまくいきます!

(または、そのリンケージはFedora 23などではうまく機能しましたが、Ubuntu 16.04では失敗します)

これは、リンケージが機能するディストリビューションが、共有ライブラリを必要に応じてリンクするようにGCCツールチェーンを構成しないディストリビューションの1つだからです

かつて、UNIXライクなシステムでは、静的ライブラリと共有ライブラリを異なるルールでリンクするのが普通でした。リンケージシーケンスの静的ライブラリは、例1で説明した必要に応じてリンクされましたが、共有ライブラリは無条件にリンクされました。

リンカはプログラムで共有ライブラリが必要かどうかを考える必要がないため、この動作はリンク時に経済的です。共有ライブラリの場合はリンクします。そして、ほとんどのリンケージのほとんどのライブラリは共有ライブラリです。しかし、欠点もあります:-

  • 実行時に共有ライブラリを必要としない場合でも、プログラムと一緒に読み込まれる可能性があるため、これは実行時に不経済です。

  • 静的ライブラリと共有ライブラリの異なるリンケージルール-lfooは、リンケージがに解決されるの/some/where/libfoo.aかに解決されるのか/some/where/libfoo.soわからず、共有ライブラリと静的ライブラリの違いを理解できない未熟なプログラマを混乱させる可能性があります。

このトレードオフは、今日の分裂的な状況につながっています。一部のディストリビューションでは、必要に応じて 原則をすべてのライブラリに適用できるように、共有ライブラリのGCCリンケージルールを変更しています。一部のディストリビューションは古い方法に固執しています。

コンパイルとリンクを同時に行ってもこの問題が発生するのはなぜですか?

私がやった場合:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

確かにeg1.c最初にgccをコンパイルしてから、結果のオブジェクトファイルをにリンクする必要がありますlibmy_lib.a。それでは、リンクを行うときにオブジェクトファイルが必要であることをどのようにして知ることができないのでしょうか。

単一のコマンドでコンパイルおよびリンクしても、リンケージシーケンスの順序は変更されないためです。

上記のコマンドを実行すると、gccコンパイル+リンケージが必要であることがわかります。だから、舞台裏、それはコンパイルコマンドを生成し、それを実行し、その後、リンクコマンドを生成し、そしてかのように、それを実行し、あなたが 2つのコマンドを実行していました:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

だから、リンケージは、あなたがあればそれと同じように失敗しないこれらの2つのコマンドを実行します。この失敗で気が付く唯一の違いは、gccがcompile + linkの場合に一時オブジェクトファイルを生成したことですeg1.o。これは、使用するように指示していないためです。私たちは見る:

/tmp/ccQk1tvs.o: In function `main'

の代わりに:

eg1.o: In function `main':

こちらもご覧ください

相互に依存するリンクライブラリが指定されている順序が間違っている

相互に依存するライブラリを間違った順序で配置することは、リンケージの中で、定義を提供するファイルよりも後に来るものの定義を必要するファイルを取得できる1つの方法にすぎません。ライブラリを参照するオブジェクトファイルの前にライブラリを置くことも、同じ間違いを犯す別の方法です。


18

テンプレートを友だちにしています...

フレンド演算子(または関数)を含むテンプレートタイプのコードスニペットが与えられます。

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

operator<<非テンプレート関数として宣言されています。でT使用されるすべてのタイプについてFoo、テンプレート化されていないが必要operator<<です。たとえば、型がFoo<int>宣言されている場合、次のような演算子の実装が必要です。

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

実装されていないため、リンカはそれを見つけることができず、エラーが発生します。

これを修正するには、Foo型の前にテンプレート演算子を宣言してから、適切なインスタンス化であるフレンドとして宣言します。構文は少し厄介ですが、次のようになります。

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

上記のコードは、オペレーターの友情を対応するのインスタンス化に制限します。Fooつまり、operator<< <int>インスタンス化は、のインスタンス化のプライベートメンバーへのアクセスに制限されていますFoo<int>

代替案には以下が含まれます。

  • 次のように、友情がテンプレートのすべてのインスタンス化に拡張できるようにします。

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • または、operator<<クラス定義内でインラインでを実装することもできます。

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };

演算子(または関数)の宣言はクラスのみに表示され、名前はから、検索、唯一の引数依存のルックアップのための「ノーマル」には使用できませんcppreference

クラスまたはクラステンプレートX内のフレンド宣言で最初に宣言された名前は、Xの最も内側を囲む名前空間のメンバーになりますが、名前空間スコープでの一致する宣言がない限り、ルックアップ(Xを考慮する引数依存ルックアップを除く)にはアクセスできません提供されました...

cppreferenceC ++ FAQにあるテンプレートフレンドの詳細については、こちらをご覧ください

上記の手法を示すコードリスト


失敗したコードサンプルの補足として。g ++はこれについて次のように警告します

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)


16

インクルードパスが異なる場合

ヘッダーファイルとそれに関連付けられた共有ライブラリ(.libファイル)が同期しなくなった場合、リンカーエラーが発生する可能性があります。説明させてください。

リンカーはどのように機能しますか?リンカーは、シグニチャーを比較することにより、関数宣言(ヘッダーで宣言)とその定義(共有ライブラリー)を照合します。リンカーが完全に一致する関数定義を見つけられない場合、リンカーエラーが発生する可能性があります。

宣言と定義が一致しているように見えても、リンカーエラーが発生する可能性はありますか?はい!それらはソースコードでは同じに見えるかもしれませんが、それは実際にはコンパイラーが何を見るかに依存します。基本的に、次のような状況になる可能性があります。

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

どちらの関数宣言もソースコードでは同じに見えますが、コンパイラーによって実際には異なっていることに注意してください。

あなたはそのような状況にどのように終わるか尋ねるかもしれませんか?もちろんインクルードパス!共有ライブラリをコンパイルするときに、インクルードパスがつながり、header1.h最終的にheader2.h独自のプログラムで使用する場合、何が起こったのかと思ってヘッダーをひっくり返すことになります(しゃれつもり)。

これが現実の世界でどのように起こり得るかの例を以下に説明します。

例を使ってさらに詳しく

私には2つのプロジェクトがgraphics.libありmain.exeます。どちらのプロジェクトもに依存していcommon_math.hます。ライブラリが次の関数をエクスポートするとします。

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

次に、ライブラリを自分のプロジェクトに含めます。

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

ブーム!リンカエラーが発生し、それが失敗する理由がわかりません。その理由は、共通ライブラリが同じインクルードの異なるバージョンを使用しているためですcommon_math.h(ここでは、例で異なるパスを含めることでそれを明確にしましたが、必ずしもそれほど明確ではない場合があります。インクルードパスはコンパイラ設定で異なる場合があります)。 。

この例ではdraw()、実際にはライブラリによってエクスポートされていることがわかっている場合でも、リンカーはを見つけられなかったと通知します。何がうまくいかないのかと頭を悩ませるのに何時間も費やす可能性があります。問題は、パラメーターの型がわずかに異なるため、リンカーは異なるシグネチャを参照することです。この例でvec3は、コンパイラに関する限り、両方のプロジェクトでタイプが異なります。これは、2つのわずかに異なるインクルードファイルからのものである可能性があります(インクルードファイルは、2つの異なるバージョンのライブラリからのものである可能性があります)。

リンカのデバッグ

Visual Studioを使用している場合、DUMPBINはあなたの友達です。他のコンパイラにも同様のツールがあると思います。

プロセスは次のようになります。

  1. リンカエラーで指定された変なマングル名に注意してください。(例:draw @ graphics @ XYZ)。
  2. ライブラリからエクスポートされたシンボルをテキストファイルにダンプします。
  3. エクスポートされた目的のシンボルを検索し、マングルされた名前が異なることに注意してください。
  4. マングルされた名前が異なる結果になった理由に注意してください。ソースコードで同じに見えても、パラメーターの型が異なることがわかります。
  5. それらが異なる理由。上記の例では、インクルードファイルが異なるため、これらは異なります。

[1]プロジェクトとは、ライブラリまたは実行可能ファイルを生成するためにリンクされた一連のソースファイルを意味します。

編集1:理解しやすくなるように最初のセクションを書き直しました 他に修正が必要な場合は、以下にコメントしてください。ありがとう!


15

矛盾したUNICODE定義

A WindowsのUNICODEビルドがで構築されたTCHARように定義されているなどwchar_tでビルドしない場合などUNICODEに、ビルドとして定義TCHARとして定義されchar、これらなどUNICODE_UNICODE定義は、すべての影響を与えるT」文字列型をLPTSTRLPCTSTRそして彼らのヘラジカ。

UNICODE定義済みのライブラリを1つ構築し、それがUNICODE定義されていないプロジェクトでリンクしようとすると、の定義に不一致があるため、リンカーエラーが発生しTCHARます。charwchar_t

エラーには通常、関数、charまたはwchar_t派生型の値が含まstd::basic_string<>れますが、これらにも同様に含まれる可能性があります。コード内の影響を受ける関数を参照するときTCHARstd::basic_string<TCHAR>などへの参照がよくあります。これは、コードが最初にUNICODEとマルチバイト文字(または「狭い」)ビルドの両方を意図していたことを示す証拠です。

これを修正するには、UNICODE(および_UNICODE)の一貫した定義を使用して、必要なすべてのライブラリとプロジェクトをビルドします。

  1. これはどちらかで行うことができます。

    #define UNICODE
    #define _UNICODE
  2. またはプロジェクト設定で;

    プロジェクトプロパティ>一般>プロジェクトデフォルト>文字セット

  3. またはコマンドラインで。

    /DUNICODE /D_UNICODE

代替手段も適用可能です。UNICODEを使用する予定がない場合は、定義が設定されていないこと、および/またはプロジェクトで複数文字の設定が使用され、一貫して適用されていることを確認してください。

「リリース」ビルドと「デバッグ」ビルドの間でも一貫していることを忘れないでください。


14

クリーンと再構築

ビルドの「クリーン」は、以前のビルド、失敗したビルド、不完全なビルド、およびその他のビルドシステム関連のビルドの問題から取り残されている可能性がある「デッドウッド」を削除できます。

一般に、IDEまたはビルドには何らかの「クリーン」機能が含まれますが、これは正しく構成されていない場合(手動のmakefileなど)、または失敗する場合があります(中間または結果のバイナリが読み取り専用など)。

「クリーン」が完了したら、「クリーン」が成功し、生成されたすべての中間ファイル(たとえば、自動化されたmakefile)が正常に削除されたことを確認します。

このプロセスは最終的な手段と見なすことができますが、多くの場合、最初のステップとして適切です。特に、エラーに関連するコードが最近追加された場合(ローカルまたはソースリポジトリから)。


10

const変数の宣言/定義に「extern」がない(C ++のみ)

C出身の人にとって、C ++ではグローバルconst変数に内部(または静的)リンケージがあることは驚くかもしれません。Cではこれは当てはまりませんでした。すべてのグローバル変数が暗黙的にextern(つまり、staticキーワードがない場合)です。

例:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

正しいのは、ヘッダーファイルを使用して、file2.cpp file1.cppに含めることです。

extern const int test;
extern int test2;

あるいはconst、file1.cppで変数を明示的に宣言することもできますextern


8

これはかなり古い質問ですが、複数の回答が受け入れられていますが、あいまいなものを解決する方法を共有したいと思います「未定義の参照」エラーます。

ライブラリの異なるバージョン

私はエイリアスを使用して参照していましたstd::filesystem::path:C ++ 17以降、ファイルシステムは標準ライブラリにありますが、プログラムはC ++ 14でもコンパイルする必要があるため、変数エイリアスを使用することにしました。

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif

main.cpp、file.h、file.cppの3つのファイルがあるとします。

  • file.h #includeの< experimental :: filesystem >および上記のコードが含まれています
  • file.cppfile.hの実装、#includeの " file.h "
  • main.cpp #includeの< filesystem >および " file.h "

main.cppとfile.hで使用されているさまざまなライブラリに注意してください。main.cpp #includeされた「file.h」が< filesystem >の後にあるため、そこで使用されているファイルシステムのバージョンはC ++ 17のものでした。以前は、次のコマンドを使用してプログラムをコンパイルしていました。

$ g++ -g -std=c++17 -c main.cpp-> main.cppをmain.oにコンパイルします
$ g++ -g -std=c++17 -c file.cpp-> file.cppとfile.hをfile.oにコンパイルします
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> main.oとfile.oをリンクします

この方法は、任意の関数ことfile.oに含まれ、main.oで使用される必要がpath_tあるため、「未定義参照」エラーを与えmain.oを呼ぶstd::filesystem::pathが、file.oしますstd::experimental::filesystem::path

解決

これを修正するには、file.hの<experimental :: filesystem>を<filesystem>変更する必要がありました。


5

共有ライブラリに対してリンクする場合は、使用されているシンボルが非表示になっていないことを確認してください。

gccのデフォルトの動作では、すべてのシンボルが表示されます。ただし、翻訳単位がoption -fvisibility=hiddenで作成されている場合、__attribute__ ((visibility ("default")))結果としての共有オブジェクトでは、でマークされた関数/シンボルのみが外部になります。

あなたが探しているシンボルが外部のものであるかどうかを確認するには、以下を呼び出します:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

非表示/ローカルシンボルはnm小文字のシンボルタイプで示されます。たとえばt、コードセクションの `Tではなく、

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

nmオプション-Cと一緒に使用して名前をデマングルすることもできます(C ++が使用されている場合)。

Windows-dllと同様に、たとえばDLL_PUBLIC次のように定義された定義でパブリック関数をマークします。

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

これは、おおよそWindows / MSVCバージョンに対応しています。

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

可視性の詳細については、gcc wikiを参照してください。


-fvisibility=hidden結果のシンボルで翻訳単位がコンパイルされると、外部リンケージ(大文字のシンボルタイプでで示されるnm)がまだあり、オブジェクトファイルが静的ライブラリの一部になっていれば、外部リンケージに問題なく使用できます。オブジェクトファイルが共有ライブラリにリンクされている場合にのみ、リンケージがローカルになります。

非表示になっているオブジェクトファイルのシンボルを見つけるには、次のコマンドを実行します。

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

外部シンボルを表示するには、nm -CDまたはnm -gCDを使用する必要があります。GCC wikiのVisibilityも参照してください。
jww 2018

2

異なるアーキテクチャ

次のようなメッセージが表示される場合があります。

library machine type 'x64' conflicts with target machine type 'X86'

その場合、利用可能なシンボルは、コンパイル対象のアーキテクチャとは異なるアーキテクチャのものであることを意味します。

Visual Studioでは、これは誤った「プラットフォーム」が原因であり、適切なものを選択するか、適切なバージョンのライブラリをインストールする必要があります。

Linuxでは、誤ったライブラリフォルダーが原因である可能性があります(たとえば、のlib代わりにlib64を使用)。

MacOSでは、両方のアーキテクチャを同じファイルで出荷するオプションがあります。リンクは両方のバージョンが存在することを期待している可能性がありますが、1つだけです。また、ライブラリが取得される場所が間違っているlib/ lib64フォルダーの問題である可能性もあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.