C言語/ストレージクラス指定子
ストレージクラス指定子の概要
編集プログラムには、様々な種類の変数や関数が存在します。ストレージクラス指定子とは、それらの変数や関数の性質を指定するための修飾子のことです。主な指定できる性質には、格納期間、リンケージ、値、型があります。
- 格納期間とは、その変数や関数が存在し続ける期間のことです。
- リンケージとは、その名前がプログラムのどの範囲から参照可能かを指定するものです。
- 値とは、変数が定数として扱えるかどうかを指定します。
- 型とは、新しい型の別名を定義することです。
ストレージクラス指定子の種類
編集Cでは以下の7種類のストレージクラス指定子が用意されています。
auto
constexpr
extern
register
static
thread_local
typedef
ストレージクラス指定子の構文
編集ストレージクラス指定子は、変数や関数を宣言する際に、型指定の前に記述します。
storage-class-specifier type variable;
複数のストレージクラス指定子を組み合わせて指定することも可能ですが、規則があります。
格納期間を指定するストレージクラス指定子
編集格納期間を指定するストレージクラス指定子には、auto
、static
、thread_local
があります。
auto
編集auto
を指定すると、その変数は自動格納期間を持ちます。自動格納期間とは、変数が宣言されたブロックの範囲内でのみ存在し続ける期間のことです。関数の引数に対してauto
を明示的に指定することもでき、その場合は型推論に使われます。
static
編集static
を指定すると、その変数や関数は静的格納期間を持ちます。静的格納期間とは、プログラムの実行時間中ずっと存在し続ける期間のことです。static
はファイル内での内部リンケージも指定します。
thread_local
編集thread_local
を指定すると、その変数はスレッド固有の格納期間を持ちます。つまり、その変数の値はスレッド間で共有されません。
リンケージを指定するストレージクラス指定子
編集リンケージを指定するストレージクラス指定子には、extern
と static
があります。
extern
編集extern
を指定すると、その変数や関数は外部リンケージを持ちます。外部リンケージとは、その名前が翻訳単位の外から参照可能になることを意味します。
static
編集前述のようにstatic
は、ファイル内での内部リンケージも指定します。内部リンケージとは、その名前がファイル内からしか参照できないことを意味します。
値を指定するストレージクラス指定子
編集constexpr
編集constexpr
を指定すると、その変数は定数式として扱われる値を持ちます。つまり、その変数は一度初期化されると値が変更できなくなります。constexpr
で宣言した変数は、コンパイル時に値が計算され、その値がオブジェクトコードに組み込まれます。
constexpr
には多くの制約があります。例えば、浮動小数点数の初期化子は、丸め動作が実装定義であるため、制約に違反する可能性があります。また、constexpr
オブジェクトには一部の型(アトミック型、可変長配列型、volatile
修飾型など)を使うことができません。
型を指定するストレージクラス指定子
編集typedef
編集typedef
を使うと、既存の型に新しい名前(エイリアス)を付けることができます。これにより、複雑な構造体などの型を簡潔に記述できるようになります。
typedef unsigned char BYTE; BYTE b; // unsigned charの別名
個別のストレージクラス指定子
編集constexpr
編集constexpr
の詳細は、前述の「値を指定するストレージクラス指定子」の項で説明しています。制約が多いため、constexpr
を使う際は注意が必要です。
extern
編集extern
で宣言された変数や関数は、外部リンケージを持ちます。外部リンケージを持つ変数や関数の定義は、プログラム中の1か所にのみ存在できます。
- file1.c
extern int global_var; // 外部変数の宣言
- file2.c
extern int global_var; // 同じ外部変数の宣言 int global_var = 0; // 外部変数の定義
register
編集register
は、その変数をレジスタに格納することを示唆する指定子です。しかし、現代のコンパイラではレジスタ割り当ての最適化が行われるため、register
を指定しても無視されることが多いです。
static
編集static
の詳細は、前述の「格納期間を指定するストレージクラス指定子」と「リンケージを指定するストレージクラス指定子」の項で説明しています。static
は静的格納期間と内部リンケージの両方を指定します。
thread_local
編集thread_local
で宣言された変数は、スレッド固有の格納期間を持ちます。つまり、その変数の値は同じプロセス内の別スレッドからは参照できますが、値は共有されません。
複数の翻訳単位でthread_local
を指定する場合、すべての翻訳単位でthread_local
を指定する必要があります。
// file1.c thread_local int tl_var; // thread_local変数の宣言 // file2.c extern thread_local int tl_var; // 同じthread_local変数の宣言
ストレージクラス指定子の組み合わせ
編集1つの変数や関数の宣言において、複数のストレージクラス指定子を組み合わせて指定することができますが、以下の規則があります。
- 最大1つのストレージクラス指定子しか指定できない。ただし、以下の組み合わせは例外。
thread_local
はstatic
またはextern
と組み合わせて指定可能auto
はtypedef
以外のすべてと組み合わせて指定可能constexpr
はauto
、register
、static
と組み合わせて指定可能
- オブジェクトの宣言で
thread_local
を指定する場合は、必ず `static
またはextern
も併せて指定しなければならない。 - オブジェクトの宣言で1度でも
thread_local
を指定していれば、そのオブジェクトのすべての宣言でthread_local
を指定しなければならない。 - 関数の宣言では
thread_local
は指定できない。 auto
は、ファイルスコープの識別子宣言、または型推論を行う場合にのみ指定可能。
これらの規則に従わない組み合わせを指定した場合、コンパイルエラーになります。
例
編集ストレージクラス指定子の使用例を示します。
// auto void func(int x) { auto int y = x; // 型推論を行う // ... } // constexpr constexpr double pi = 3.141592; // extern extern int global_var; // 外部変数の宣言 // register (殆ど無視される) register int r; // static static int file_static = 0; // 内部リンケージと静的格納期間 // thread_local thread_local static int tl_var = 0; // スレッド固有の格納期間 // typedef typedef unsigned int WORD; WORD w;
このように、状況に応じて適切なストレージクラス指定子を選んで使い分ける必要があります。特に constexpr
には多くの制約があり、注意が必要です。