記憶域クラス指定子
記憶域クラス指定子は、名前の宣言構文における decl-specifier-seq の一部です。これらは名前のスコープと共に、名前の2つの独立したプロパティ、すなわちその記憶域期間とリンケージを制御します。
目次 |
[編集] 記憶域期間
記憶域期間とは、オブジェクトを格納する記憶域の最小潜在寿命を定義するオブジェクトのプロパティです。記憶域期間は、オブジェクトの作成に使用されたコンストラクトによって決定され、次のいずれかになります。
- 静的記憶域期間
|
(C++11以降) |
- 自動記憶域期間
- 動的記憶域期間
静的、スレッド、(C++11以降)および自動記憶域期間は、宣言によって導入されたオブジェクトや一時オブジェクトに関連付けられます。動的記憶域期間は、new 式によって作成されたオブジェクトや、暗黙的に作成されたオブジェクトに関連付けられます。
記憶域期間のカテゴリは参照にも適用されます。
サブオブジェクトおよび参照メンバーの記憶域期間は、それらの完全オブジェクトの記憶域期間と同じです。
[編集] 指定子
以下のキーワードは記憶域クラス指定子です。
|
(C++11まで) |
|
(C++17まで) |
- static
|
(C++11以降) |
- extern
- mutable
decl-specifier-seq には、記憶域クラス指定子は最大1つしか指定できませんが、thread_local は static または extern と一緒に現れることができます(C++11以降)。
mutable は記憶域期間に影響を与えません。その使用法については、const/volatile を参照してください。
その他の記憶域クラス指定子は、以下の宣言の decl-specifier-seq に現れることができます。
| 指定子 | decl-specifier-seqに現れることができる | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| 変数宣言 | 関数宣言 | 構造化束縛宣言 (C++17以降) | |||||||
| 非メンバー | メンバ | 非メンバー | メンバ | ||||||
| 非パラメータ | 関数パラメータ | 非静的 | 静的 | 非静的 | 静的 | ||||
| auto | ブロックスコープのみ | はい | いいえ | いいえ | いいえ | いいえ | いいえ | N/A | |
| register | ブロックスコープのみ | はい | いいえ | いいえ | いいえ | いいえ | いいえ | N/A | |
| static | はい | いいえ | 静的宣言 | 名前空間スコープのみ | 静的宣言 | はい | |||
| thread_local | はい | いいえ | いいえ | はい | いいえ | いいえ | いいえ | はい | |
| extern | はい | いいえ | いいえ | いいえ | はい | いいえ | いいえ | いいえ | |
無名共用体も static を付けて宣言できます。
|
register は、宣言された変数が頻繁に使用されるため、その値をCPUレジスタに格納できるというヒントです。このヒントは無視されることがあり、ほとんどの実装では変数のアドレスが取られると無視されます。この使用法は非推奨です。 |
(C++17まで) |
[編集] 静的記憶域期間
以下のすべての条件を満たす変数は、静的記憶域期間を持ちます。
- 名前空間スコープに属するか、最初に static または extern で宣言されていること。
|
(C++11以降) |
これらのエンティティの記憶域は、プログラムの存続期間全体にわたって持続します。
スレッド記憶域期間thread_local で宣言されたすべての変数はスレッド記憶域期間を持ちます。 これらのエンティティの記憶域は、それらが作成されたスレッドの存続期間全体にわたって持続します。スレッドごとに異なるオブジェクトまたは参照があり、宣言された名前の使用は現在のスレッドに関連付けられたエンティティを参照します。 |
(C++11以降) |
[編集] 自動記憶域期間
以下の変数は自動記憶域期間を持ちます。
- ブロックスコープに属し、明示的に static、thread_local、(C++11以降)または extern と宣言されていない変数。このような変数の記憶域は、それらが作成されたブロックを抜けるまで持続します。
- パラメータスコープに属する変数(すなわち、関数パラメータ)。関数パラメータの記憶域は、その破棄直後まで持続します。
[編集] 動的記憶域期間
プログラム実行中に以下の方法で作成されたオブジェクトは、動的記憶域期間を持ちます。
- new 式。このようなオブジェクトの記憶域は、割り当て関数によって割り当てられ、解放関数によって解放されます。
- その他の手段による暗黙的な作成。このようなオブジェクトの記憶域は、既存の記憶域と重複します。
- 例外オブジェクト。このようなオブジェクトの記憶域は、未指定の方法で割り当てられ、解放されます。
[編集] リンケージ
名前は外部リンケージ、モジュールリンケージ(C++20以降)、内部リンケージ、またはリンケージなしを持つことができます。
- 名前が外部リンケージを持つエンティティは、別の翻訳単位で再宣言することができます。その際、再宣言は異なるモジュールにアタッチできます(C++20以降)。
|
(C++20以降) |
- 名前が内部リンケージを持つエンティティは、同じ翻訳単位内の別のスコープで再宣言できます。
- 名前がリンケージを持たないエンティティは、同じスコープ内でのみ再宣言できます。
以下のリンケージが認識されます。
[編集] リンケージなし
ブロックスコープで宣言された以下の名前はリンケージを持ちません。
- 明示的に extern と宣言されていない変数(static 修飾子があるかどうかに関わらず)。
- ローカルクラスとそのメンバ関数。
- typedef、列挙型、列挙子など、ブロックスコープで宣言されたその他の名前。
外部リンケージ、モジュールリンケージ、(C++20以降)または内部リンケージが指定されていない名前も、どのスコープで宣言されたかに関わらず、リンケージを持ちません。
[編集] 内部リンケージ
名前空間スコープで宣言された以下の名前は内部リンケージを持ちます。
- static と宣言された変数、変数テンプレート(C++14以降)、関数、または関数テンプレート。
- テンプレートではない (C++14以降)非 volatile const 修飾型の変数。ただし、以下の場合を除く。
|
(C++17以降) |
|
(C++20以降) |
- 明示的に extern と宣言されている場合。
- 以前に宣言されており、その以前の宣言が内部リンケージを持たなかった場合。
- 無名共用体のデータメンバー。
|
さらに、無名名前空間または無名名前空間内の名前空間で宣言されたすべての名前は、明示的に extern と宣言されていても、内部リンケージを持ちます。 |
(C++11以降) |
[編集] 外部リンケージ
外部リンケージを持つ変数と関数は、言語リンケージも持ち、異なるプログラミング言語で書かれた翻訳単位をリンクすることを可能にします。
名前空間スコープで宣言された以下の名前は外部リンケージを持ちます。ただし、無名名前空間で宣言されている場合、またはその宣言が名前付きモジュールにアタッチされており、エクスポートされていない場合(C++20以降)は除きます。
- 上記にリストされていない変数および関数(すなわち、static と宣言されていない関数、static と宣言されていない非 const 変数、およびextern と宣言された変数)。
- 列挙型。
- クラスの名前、そのメンバ関数、静的データメンバ(const かどうかにかかわらず)、ネストされたクラスと列挙型、およびクラス本体内でfriend 宣言によって最初に導入された関数。
- 上記にリストされていないすべてのテンプレートの名前(すなわち、static と宣言された関数テンプレートではないもの)。
ブロックスコープで最初に宣言された以下の名前は外部リンケージを持ちます。
- extern と宣言された変数の名前。
- 関数の名前。
モジュールリンケージ名前空間スコープで宣言された名前は、その宣言が名前付きモジュールにアタッチされており、エクスポートされておらず、内部リンケージを持たない場合、モジュールリンケージを持ちます。 |
(C++20以降) |
| このセクションは未完成です 理由:同じ翻訳単位内で異なるリンケージでエンティティが宣言された場合の動作の説明を追加(6.6パラグラフ6)、C++20(形式不正)と現在のドラフト(形式適合)の違いに注意。 |
[編集] 静的ブロックスコープ変数
静的またはスレッド(C++11以降)記憶域期間を持つブロックスコープ変数は、その宣言を制御が初めて通過したときに初期化されます(初期化がゼロ初期化または定数初期化であり、ブロックに初めて入る前に実行できる場合を除く)。それ以降のすべての呼び出しでは、宣言はスキップされます。
- 初期化が例外をスローした場合、変数は初期化されたとは見なされず、制御が次に宣言を通過したときに初期化が再度試行されます。
- 初期化が変数が初期化されているブロックに再帰的に入った場合、動作は未定義です。
|
(C++11以降) |
静的記憶域期間を持つブロックスコープ変数のデストラクタは、初期化が正常に行われた場合にのみ、プログラム終了時に呼び出されます。
同じインライン関数(暗黙的にインラインである場合も含む)のすべての定義における静的記憶域期間を持つ変数は、関数が外部リンケージを持つ限り、1つの翻訳単位で定義された同じオブジェクトを参照します。
[編集] 翻訳単位ローカルなエンティティ
翻訳単位ローカルなエンティティの概念はC++20で標準化されました。詳細はこのページを参照してください。
エンティティは、以下のいずれかに該当する場合、翻訳単位ローカル(または略してTU-ローカル)です。
- 内部リンケージを持つ名前を持つ場合、または
- リンケージを持つ名前を持たず、TU-ローカルなエンティティの定義内で導入されている場合、または
- テンプレート引数またはテンプレート宣言がTU-ローカルなエンティティを使用するテンプレートまたはテンプレート特殊化である場合。
非TU-ローカルなエンティティの型がTU-ローカルなエンティティに依存する場合、または非TU-ローカルなエンティティの宣言、あるいはその推論ガイドが、(C++17以降)TU-ローカルなエンティティをその外部で参照する場合、良くないこと(通常はODR違反)が発生する可能性があります。
- 非インライン関数または関数テンプレートの関数本体
- 変数または変数テンプレートの初期化子
- クラス定義内の friend 宣言
- 変数が定数式で使用可能な場合の変数値の使用
|
そのような使用は、モジュールインターフェース単位(もしあればそのプライベートモジュールフラグメントの外)またはモジュールパーティションでは許可されず、他のあらゆるコンテキストでは非推奨です。 ある翻訳単位に現れる宣言は、ヘッダー単位ではない別の翻訳単位で宣言されたTU-ローカルなエンティティを名前で参照することはできません。テンプレートのためにインスタンス化された宣言は、特殊化のインスタンス化の時点に現れます。 |
(C++20以降) |
[編集] 備考
トップレベルの名前空間スコープ(Cではファイルスコープ)で const であり extern ではない名前は、Cでは外部リンケージを持ちますが、C++では内部リンケージを持ちます。
C++11以降、auto は記憶域クラス指定子ではなくなりました。型推論を示すために使用されます。
|
Cでは register 変数のアドレスは取得できませんが、C++では register と宣言された変数は、記憶域クラス指定子なしで宣言された変数と意味的に区別できません。 |
(C++17まで) |
|
C++では、Cとは異なり、変数を register と宣言することはできません。 |
(C++17以降) |
内部または外部リンケージを持つ thread_local 変数の名前は、異なるスコープから参照される場合、コードが同じスレッドで実行されているか異なるスレッドで実行されているかによって、同じインスタンスを参照することも、異なるインスタンスを参照することもあります。
extern キーワードは、言語リンケージや明示的なテンプレートインスタンス化宣言を指定するためにも使用できますが、これらの場合、記憶域クラス指定子ではありません(ただし、宣言が言語リンケージ指定に直接含まれる場合を除きます。その場合、宣言は extern 指定子を含むかのように扱われます)。
thread_local を除く記憶域クラス指定子は、明示的な特殊化および明示的なインスタンス化には許可されません。
template<class T> struct S { thread_local static int tlm; }; template<> thread_local int S<float>::tlm = 0; // "static" does not appear here
|
const (constexpr によって暗黙的に示される場合があります)変数テンプレートは、デフォルトで内部リンケージを持つとされていましたが、これは他のテンプレートエンティティと矛盾していました。欠陥報告CWG2387によって修正されました。 |
(C++14以降) |
inline は、デフォルトで外部リンケージを与えることで、CWG2387 の回避策として機能します。これが、多くの変数テンプレートに inline が追加され、CWG2387 が承認された後に削除された理由です。サポートされるコンパイラが CWG2387 を実装するまでは、標準ライブラリの実装も inline を使用する必要があります。詳細については、GCC Bugzilla #109126 および MSVC STL PR #4546 を参照してください。 |
(C++17以降) |
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_threadsafe_static_init |
200806L |
(C++11) | 動的初期化と同時実行性のある破棄 |
[編集] キーワード
auto, register, static, extern, thread_local, mutable
[編集] 例
#include <iostream> #include <mutex> #include <string> #include <thread> thread_local unsigned int rage = 1; std::mutex cout_mutex; void increase_rage(const std::string& thread_name) { ++rage; // modifying outside a lock is okay; this is a thread-local variable 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++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 216 | C++98 | クラススコープ内の無名クラスと列挙型は、 名前空間スコープ内のものとは異なるリンケージを持つ |
それらはすべて外部リンケージを持つ これらのスコープではリンケージを持つ |
| CWG 389 | C++98 | リンケージを持たない名前を、 リンケージを持つエンティティの宣言に使用すべきではない |
リンケージを持たない型を、 リンケージを持つ変数または関数の型として使用してはならない ただし、その変数または関数が C言語リンケージを持つ場合は除く |
| CWG 426 | C++98 | 同じ翻訳単位内で、エンティティを内部リンケージと 外部リンケージの両方で宣言できる |
この場合、プログラムは不適格である |
| CWG 527 | C++98 | CWG 389の解決策によって導入された型制限が、 自身の翻訳単位の外では名前を付けられない 変数や関数にも適用されていた |
この制限はこれらの変数や関数に対して解除される (すなわち、リンケージなし、内部リンケージ、または無名名前空間内で宣言されたもの) リンケージまたは内部リンケージを持つか、 無名名前空間内で宣言されたもの) |
| CWG 809 | C++98 | register はほとんど機能しなかった | deprecated |
| CWG 1648 | C++11 | thread_local が extern と 組み合わされた場合でも static が暗黙的に指定された |
他の記憶域クラス指定子が存在しない場合にのみ 暗黙的に指定される |
| CWG 1686 | C++98 C++11 |
名前空間スコープで宣言された非静的変数の名前は、 明示的に const (C++98) または constexpr (C++11) と 宣言された場合にのみ内部リンケージを持っていた |
型が const-qualified であることのみを 要求した |
| CWG 2019 | C++98 | 参照メンバの記憶域期間が 未指定であった |
完全オブジェクトと同じ |
| CWG 2387 | C++14 | const 修飾された変数テンプレートが デフォルトで内部リンケージを持つか不明確であった |
const 修飾子は変数テンプレートまたは そのインスタンスのリンケージに影響を与えない そのインスタンスのリンケージに影響を与えない |
| CWG 2533 | C++98 | 暗黙的に作成されたオブジェクトの 記憶域期間が不明確であった |
明確化された |
| CWG 2850 | C++98 | 関数パラメータの記憶域がいつ 解放されるか不明確であった |
明確化された |
| CWG 2872 | C++98 | 「参照できる」の意味が不明確であった | 文言が改善された |
| P2788R0 | C++20 | 名前空間で const 修飾された変数を宣言すると、 モジュール単位でも内部リンケージが与えられた |
内部リンケージは与えられない |
[編集] 参照
- C++23標準 (ISO/IEC 14882:2024)
- 6.7.5 記憶域期間 [basic.stc]
- C++20 standard (ISO/IEC 14882:2020)
- 6.7.5 記憶域期間 [basic.stc]
- C++17 standard (ISO/IEC 14882:2017)
- 6.7 記憶域期間 [basic.stc]
- C++14 standard (ISO/IEC 14882:2014)
- 3.7 記憶域期間 [basic.stc]
- C++11 standard (ISO/IEC 14882:2011)
- 3.7 記憶域期間 [basic.stc]
- C++03 標準 (ISO/IEC 14882:2003)
- 3.7 記憶域期間 [basic.stc]
- C++98 標準 (ISO/IEC 14882:1998)
- 3.7 記憶域期間 [basic.stc]
[編集] 関連項目
| C 言語ドキュメントの記憶域期間
|