Undefined symbols for architecture x86_64:の原因(複数)

このようなエラーが発生してしまった。

Undefined symbols for architecture x86_64:
  "test()", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

根本原因

リンク時にエラーが発生している。ソースコード内で利用している関数やグローバル変数の実体が見つからないことが原因である。

エラーの要因と解決方法は複数ある

関数名が間違っている

以下の警告が表示されている場合は、関数名が間違っている可能性がある。正しい名称に直せばよい。

// warning: implicit declaration of function 'tset' is invalid in C99 [-Wimplicit-function-declaration]
tset(); // タイプミス

test(); // これならOK

関数が定義されていない

プロトタイプ宣言や前方宣言、外部宣言しかされていない関数を呼び出そうとすると、本エラーが発生する原因になる。

関数の実体を定義する必要がある。

/* file.h */
extern void a(); // 宣言
/* file.c */
void a() {}      // 定義(これが必要)

C++メンバ関数でも同様の問題が発生する。

/* file.hpp */
struct A {
  void a();   // 宣言
  void b() {} // 宣言 & 定義
};

/* file.cpp */
void A::a() {} // 定義(これが必要)

ヘッダーファイルがインクルードされていない

利用している関数を含むヘッダーファイルがきちんとインクルードされていないと、今回のようなエラーが発生する原因になる。ソースコード内で対象のライブラリをきちんとインクルードすれば良い。

#include <sqlite3.h>
#include <zlib.h>

外部ライブラリがリンク出来ていない

外部ライブラリを利用している場合は、きちんとリンクがされているかどうかも確認してみるとよい。

例えばSQLiteを利用している場合はclang -lsqlite3 hello.cという形でコンパイルしてみると良い。IDEを利用している場合はLinker Flagsという類の設定項目に-lsqlite3を追加してみると良い。

Xcodeの場合は、以下のいずれかの設定欄に対象のライブラリを追加することで、自動的にリンクが行われるようになる。

TARGETS → General → Linked Frameworks and Libraries
TARGETS → Build Phases → Link Binary With Libraries

C言語とC++のファイルが混在している

こちらはC++プロジェクトでの対応方法である。

C言語形式のファイルやライブラリをC++側で読み込む際に、本エラーが発生する場合がある。

C言語のシンボル名はC++と異なる規則でコンパイルされるため、両者を混在させてしまうとシンボル名の不一致が起こり、今回のようなエラーが発生してしまう原因となる。

実装ファイルをC++形式にすれば問題が解決するが、どうしても現状を維持しなければならないような場合は、ヘッダーファイル側の関数宣言部にextern "C"を加えると良い。

C++用のヘッダファイル(.hpp)を作成している場合

/* clanguage.hpp */

#ifdef __cplusplus
extern "C" void test(); // これ

// ブロック形式での一括適用も可能
extern "C" {
  void foo();
  void bar();
}
#endif

C言語/C++共用のヘッダファイル(.h)を作成している場合

/* clanguage.h */

#ifdef __cplusplus
extern "C" {
#endif

test();

#ifdef __cplusplus
}
#endif
__cplusplusプリプロセッサーマクロを用いることで、ヘッダファイルがC++からインクルードされている場合のみextern "C" {}構文を機能させることができる。

ライブラリ側のヘッダファイルが編集できない場合

/* main.cpp */

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

テンプレートの実体が存在しない

このようにテンプレート関数の宣言と実装が分かれているケースで頻繁に発生してしまう。

/* file.hpp */
template<class T> T max(T a, T b);
/* file.cpp */
template<class T> T max(T a, T b) { return a < b ? b : a; }

main関数内等でmax(1, 2);などの処理を記述すると今回のようなエラーが発生してしまうはずだ。

解決方法

明示的なインスタンス化

以下のように、実装ファイル側でテンプレート関数を明示的にシンボル化してあげれば良い。

/* file.cpp */
template int max<int>(int a, int b); // これ
template double max<double>(double a, double b); // これ

こうすることで、maxテンプレート関数はint型とdouble型の引数に対応できるようになる。

クラステンプレートに関しても同様の方法で対処できる。

/* number.hpp */
template<class T> struct Number { T value; T getValue(); };
/* number.cpp */
template<class T> T Number<T>::getValue() { return 0; }

template int Number<int>::getValue(); // これ

ちなみに、実装部をヘッダー側に書いてしまえば今回のような問題は起きない(ただし、わざわざ分離するのには理由がある場合もあるので、既存コードの書き換えは慎重に行う必要がある)。

なぜエラーになるのか

一応簡単に説明するが、これは関数テンプレートの肝心な処理部分が実装ファイル側に隠蔽されてしまっていることが原因である。

ヘッダ側で宣言部は見えていても、肝心な処理部分がmain関数側のファイルから見えないため、テンプレートの展開・実体化が行えないという単純な話だ。

そのため今回の解決策では、そのような展開できないテンプレート関数を明示的に実体化したというわけである。

広告