翻訳単位とリンケージ

C++ プログラムでは、変数や関数名など、シンボルをスコープ内で何回でも宣言できます。 ただし、シンボルは 1 回だけ定義できます。 このルールは、"One Definition Rule" (ODR) です。 宣言は、名前をプログラムに導入 (または再導入) し、後で名前を定義に関連付けるのに十分な情報を提供します。 定義は、名前を導入し、その作成に必要なすべての情報を提供します。 名前が変数を表す場合、定義によってストレージが明示的に作成され、初期化されます。 関数定義は、署名と関数本体から構成されます。 クラス定義は、クラス名の後に、すべてのクラス メンバーを一覧表示するブロックで構成されます (メンバー関数の本体は、必要に応じて別のファイルで個別に定義できます)。

次の例は、いくつかの宣言を示しています。

int i;
int f(int x);
class C;

次の例は、いくつかの定義を示しています。

int i{42};
int f(int x){ return x * i; }
class C {
public:
   void DoSomething();
};

プログラムは、1 つ以上の翻訳単位で構成されます。 翻訳単位は、実装ファイルと、それが直接または間接的に含まれるすべてのヘッダーで構成されます。 通常、実装ファイルのファイル拡張子は .cpp または .cxx です。 通常、ヘッダー ファイルの拡張子は .h または .hpp です。 各翻訳単位は、コンパイラによって個別にコンパイルされます。 コンパイルが完了すると、リンカーはコンパイルされた翻訳単位を 1 つのプログラムにマージします。 通常、ODR ルールの違反はリンカー エラーとして表示されます。 リンカー エラーは、同じ名前が複数の翻訳単位で定義されている場合に発生します。

一般に、複数のファイル間で変数を表示する最善の方法は、ヘッダー ファイルで宣言することです。 次に、宣言を #include 必要とするすべての .cpp ファイルにディレクティブを追加します。 ヘッダーの内容の周囲にインクルード ガードを追加することで、ヘッダーが宣言する名前が翻訳単位ごとに 1 回だけ宣言されるようにします。 1 つの実装ファイルでのみ名前を定義します。

C++20 では、モジュールはヘッダー ファイルの改善された代替として導入されています。

場合によっては、.cpp ファイルでグローバル変数またはクラスを宣言する必要があります。 そうした場合は、名前に含まれるリンケージの種類をコンパイラとリンカーに伝える方法が必要です。 リンケージの型は、オブジェクトの名前が 1 つのファイル内でのみ表示されるか、すべてのファイルに表示されるかを指定します。 リンケージの概念は、グローバル名にのみ適用されます。 リンケージの概念は、スコープ内で宣言されている名前には適用されません。 スコープは、関数定義やクラス定義など、囲む中かっこのセットによって指定されます。

外部リンケージと内部リンケージ

free 関数は、グローバル スコープまたは名前空間スコープで定義される関数です。 既定では、非 const グローバル変数と free 関数には外部リンケージがあります。これらは、プログラム内のすべての翻訳単位から表示されます。 その名前を持つグローバル オブジェクトは他にはありません。 内部リンケージを持つシンボルまたはリンケージがないシンボルは、それが宣言されている翻訳単位内でのみ表示されます。 名前に内部リンケージがある場合、同じ名前が別の翻訳単位に存在する可能性があります。 クラス定義または関数本体で宣言された変数にはリンケージはありません。

グローバル名を static として明示的に宣言することで、内部リンケージを持つグローバル名を強制できます。 このキーワードは、その可視性を、それが宣言されているのと同じ翻訳単位に制限します。 このコンテキストでは、static は、ローカル変数に適用された場合とは異なる何かを意味します。

次のオブジェクトには、既定で内部リンケージがあります。

  • const オブジェクト
  • constexpr オブジェクト
  • typedef オブジェクト
  • 名前空間スコープ内の static オブジェクト

const オブジェクトの外部リンケージを指定するには、extern として宣言し、値を割り当てます。

extern const int value = 42;

詳細については、「extern」を参照してください。

関連項目

基本的な概念