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言語形式のfile.cなのに、ヘッダーファイルがC++形式のfile.hppだったりすると、本エラーが発生する原因になる。

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

/* file.hpp */
#ifdef __cplusplus
extern "C" void test(); // これ
// ブロック形式での一括適用も可能
extern "C" {
  void foo();
  void bar();
}
#endif

これで、ヘッダー側の宣言とfile.c側の実装が紐付けられる。

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

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

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

/* 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関数側のファイルから見えないため、テンプレートの展開・実体化が行えないという単純な話だ。

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

広告