名前空間
変種
操作

記憶域クラス指定子

提供: cppreference.com
< cpp‎ | language
 
 
 
 

記憶域クラス指定子は名前の宣言の構文decl-specifier-seq の一部です。 名前のスコープと共に、その名前の2つの独立した性質、記憶域期間リンケージを制御します。

  • auto自動記憶域期間。
(C++11未満)
  • register自動記憶域期間。 また、そのオブジェクトをプロセッサのレジスタ内に配置するためのコンパイラへのヒントでもあります。 (非推奨)
(C++17未満)
  • static静的またはスレッド記憶域期間および内部リンケージ。
  • extern静的またはスレッド記憶域期間および外部リンケージ。
  • thread_localスレッド記憶域期間。
(C++11以上)


ひとつの宣言に記憶域クラス指定子はひとつだけ現れることができます。 ただし thread_localstatic または extern と組み合わせることができます。 (C++11以上)

目次

[編集] 性質

1) auto 指定子はブロックスコープまたは関数の引数リストで宣言されるオブジェクトに対してのみ使用できます。 自動記憶域期間を表します (これらの種類の宣言に対するデフォルトです)。 このキーワードのこの意味は C++11 で変更されました。
(C++11未満)
2) register 指定子はブロックスコープまたは関数の引数リストで宣言されるオブジェクトに対してのみ使用できます。 自動記憶域期間を表します (これらの種類の宣言に対するデフォルトです)。 また、このキーワードの存在はこの辺数の値を CPU のレジスタ内に格納するための最適化に対するヒントとして使用されることもあります。 このキーワードは C++11 で非推奨になりました。
(C++17未満)
3) static 指定子はオブジェクトの宣言 (関数の引数リスト内は除きます)、関数の宣言 (ブロックスコープのものは除きます)、および無名共用体の宣言でのみ使用できます。 クラスメンバの宣言で使用されるときは、静的メンバを宣言します。 オブジェクトの宣言で使用されるときは、静的記憶域期間を指定します (thread_local を伴う場合は除きます)。 名前空間スコープの宣言で使用されるときは、内部リンケージを指定します。
4) extern 指定子は変数および関数 (クラスメンバまたは関数の引数は除きます) の宣言でのみ使用できます。 外部リンケージを指定します。 厳密には記憶域期間に影響しませんが、自動記憶域期間のオブジェクトの定義では使用できないため、 extern なオブジェクトはすべて静的またはスレッド期間を持ちます。 また、 extern を使用し初期化子を持たない変数宣言は、定義ではありません。
5) thread_local キーワードは名前空間スコープで宣言されるオブジェクト、ブロックスコープで宣言されるオブジェクト、および静的データメンバに対してのみ使用できます。 オブジェクトがスレッド記憶域期間を持つことを表します。 内部リンケージまたは外部リンケージを指定するために (常に外部リンケージを持つ静的データメンバの場合は除きます) static または extern を組み合わせることができますが、追加の static は記憶域期間に影響しません。
(C++11以上)

[編集] 記憶域期間

プログラム内のすべてのオブジェクトは以下の記憶域期間のいずれかを持ちます。

  • 自動記憶域期間。 オブジェクトのための記憶域は囲っているコードブロックの始まりで確保され終わりで解放されます。 すべてのローカルなオブジェクトは、 staticextern または thread_local 宣言されたものを除いて、この記憶域期間を持ちます。
  • 静的記憶域期間。 オブジェクトのための記憶域はプログラムの開始時に確保されプログラムの終了時に解放されます。 オブジェクトの実体は1つだけ存在します。 名前空間スコープ (グローバル名前空間を含みます) で宣言されたすべてのオブジェクト、および static または extern を用いて宣言されたオブジェクトは、この記憶域期間を持ちます。
  • スレッド記憶域期間。 オブジェクトのための記憶域はスレッドの開始時に確保され終了時に解放されます。 それぞれのスレッドがオブジェクトの独自の実体を持ちます。 thread_local 宣言されたオブジェクトだけがこの記憶域期間を持ちます。 thread_local はリンケージを調節するために static または extern と一緒に使用することができます。
(C++11以上)
  • 動的記憶域期間。 オブジェクトのための記憶域は動的メモリ確保関数を使用することによって要求に応じて確保および解放されます。

[編集] リンケージ

オブジェクト、参照、関数、型、テンプレート、名前空間、または値を表す名前は、リンケージを持つことがあります。 名前がリンケージを持つ場合、その名前は別のスコープ内の宣言によって導入された同じ名前と同じエンティティを参照します。 同じ名前を持つ変数、関数、または他のエンティティが複数のスコープで宣言されているけれども十分なリンケージを持たない場合は、そのエンティティの実体が複数生成されます。

以下のリンケージが認識されます。

  • リンケージなし。 名前はそれが存在するスコープからのみ参照できます。
ブロックスコープで宣言される以下の名前はいずれもリンケージなしです。
  • 明示的に extern 宣言されていない変数 (static 修飾子に関係なく)。
  • ローカルクラスおよびそのメンバ関数。
  • typedef、列挙、列挙子などの、ブロックスコープで宣言されるその他の名前。
  • 内部リンケージ。 名前は現在の翻訳単位内のすべてのスコープから参照できます。
名前空間スコープで宣言される以下の名前はいずれも内部リンケージを持ちます。
  • static 宣言された変数、関数、関数テンプレート。
  • extern 宣言されておらず以前に外部リンケージを持つと宣言されていない非 volatile かつ非 inline (C++17以上)const 修飾された変数 (constexpr を含みます)。
  • 無名共用体のデータメンバ。
さらに、無名名前空間または無名名前空間内の名前空間で宣言されたすべての名前は、たとえ明示的に extern 宣言されていても、内部リンケージを持ちます。
(C++11以上)
  • 外部リンケージ。 名前は他の翻訳単位内のスコープから参照できます。 外部リンケージを持つ変数および関数は言語リンケージも持ちます。 これは異なるプログラミング言語で書かれた翻訳単位のリンクを可能とします。
名前空間スコープで宣言される以下の名前はいずれも外部リンケージを持ちます。 ただしその名前空間が無名または無名名前空間内に含まれる場合は除きます。 (C++11以上)
  • 上にない変数および関数 (つまり、 static 宣言されていない関数、 static 宣言されていない名前空間スコープの非 const な変数、および extern 宣言されたあらゆる変数)。
  • 列挙。
  • クラス、そのメンバ関数、静的データメンバ (const もそうでないものも)、ネストしたクラスおよび列挙、およびクラス本体内の friend 宣言を用いて最初に導入された関数の名前。
  • 上にないすべてのテンプレートの名前 (つまり、 static 宣言された関数テンプレート以外のもの)。
ブロックスコープで最初に宣言された以下の名前はいずれも外部リンケージを持ちます。
  • extern 宣言された変数の名前。
  • 関数の名前。

[編集] 静的ローカル変数

static 指定子を使用してブロックスコープで宣言された変数は、静的記憶域期間を持ちますが、制御がその宣言を最初に通過したときに初期化されます (その初期化がゼロ初期化または定数初期化の場合を除きます。 それらはブロックに最初に入る前に行うことができます)。 その後のすべての呼び出しにおいて、宣言はスキップされます。

初期化が例外を投げた場合、変数は初期化されたとみなされず、制御がその宣言を次回通過したときに再度初期化が試みられます。

変数を初期化中のブロックに初期化が再帰的に入った場合、動作は未定義です。

複数のスレッドが並行的に同じ静的ローカル変数の初期化を試みた場合、初期化はちょうど一度だけ発生します (似た動作が std::call_once を用いて任意の関数に対して得ることができます)。

ノート: この機能の通常の実装はダブルチェックロッキングパターンの変種を使用します。 これはすでに初期化されたローカル静的変数に対する実行時のオーバーヘッドを単一の非アトミックなブーリアンの比較に削減します。
(C++11以上)

ブロックスコープの静的変数に対するデストラクタはプログラムの終了時に呼ばれますが、初期化が行われ成功した場合に限ります。

同じインライン関数 (暗黙のインラインを含みます) のすべての定義内の関数ローカルな静的オブジェクトはすべて、ひとつの翻訳単位内で定義された同じオブジェクトを参照します。

[編集] ノート

const であり extern でないトップレベル名前空間スコープ (C ではファイルスコープ) の名前は、 C では外部リンケージを持ちますが、 C++ では内部リンケージを持ちます。

C++11 以降、 auto は記憶域クラス指定子ではなくなりました。 auto は型推定を表すために使用されます。

C では register 変数のアドレスを取ることはできませんが、 C++ では、 register 宣言された変数は記憶域クラス指定子なしで宣言された変数と意味論上区別できません。

(C++17未満)

C++ では、 C と異なり、変数は register 宣言できません。

(C++17以上)

異なるスコープから参照される内部または外部リンケージを持つ thread_local 変数の名前は、コードが同じスレッドで実行されるか別のスレッドで実行されるかによって、同じ実体または異なる実体を参照します。

extern キーワードは言語リンケージおよびテンプレートの明示的実体化の宣言を指定するために使用することもできますが、それらのケースではそれは記憶域クラス指定子ではありません (ただし言語リンケージ指定に宣言が直接含まれるときは除きます。 その場合、宣言は extern 指定子を含むかのように扱われます)。

キーワード mutable は、記憶域期間やリンケージに影響しませんが、 C++ の言語の文法上は、記憶域クラス指定子です。

記憶域クラス指定子は、 thread_local を除き、明示的特殊化および明示的実体化では使用できません。

template <class T> struct S {
    thread_local static int tlm;
};
template <> thread_local int S<float>::tlm = 0; // ここでは「static」は使用しません。

[編集] キーワード

auto, register, static, extern, thread_local

[編集]

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
 
thread_local unsigned int rage = 1; 
std::mutex cout_mutex;
 
void increase_rage(const std::string& thread_name)
{
    ++rage; // ロックの外側で変更しても大丈夫です。 これはスレッドローカル変数です。
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}
 
int main()
{
    std::thread a(increase_rage, "a"), b(increase_rage, "b");
 
    {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "Rage counter for main: " << rage << '\n';
    }
 
    a.join();
    b.join();
}

出力例:

Rage counter for a: 2
Rage counter for main: 1
Rage counter for b: 2

[編集] 関連項目

記憶域期間C言語リファレンス