C言語/ストレージクラス指定子

ストレージクラス指定子の概要

編集

プログラムには、様々な種類の変数や関数が存在します。ストレージクラス指定子とは、それらの変数や関数の性質を指定するための修飾子のことです。主な指定できる性質には、格納期間、リンケージ、値、型があります。

  • 格納期間とは、その変数や関数が存在し続ける期間のことです。
  • リンケージとは、その名前がプログラムのどの範囲から参照可能かを指定するものです。
  • とは、変数が定数として扱えるかどうかを指定します。
  • とは、新しい型の別名を定義することです。

ストレージクラス指定子の種類

編集

Cでは以下の7種類のストレージクラス指定子が用意されています。

  • auto
  • constexpr
  • extern
  • register
  • static
  • thread_local
  • typedef

ストレージクラス指定子の構文

編集

ストレージクラス指定子は、変数や関数を宣言する際に、型指定の前に記述します。

storage-class-specifier type variable;

複数のストレージクラス指定子を組み合わせて指定することも可能ですが、規則があります。

格納期間を指定するストレージクラス指定子

編集

格納期間を指定するストレージクラス指定子には、autostaticthread_localがあります。

autoを指定すると、その変数は自動格納期間を持ちます。自動格納期間とは、変数が宣言されたブロックの範囲内でのみ存在し続ける期間のことです。関数の引数に対してautoを明示的に指定することもでき、その場合は型推論に使われます。

static

編集

staticを指定すると、その変数や関数は静的格納期間を持ちます。静的格納期間とは、プログラムの実行時間中ずっと存在し続ける期間のことです。staticはファイル内での内部リンケージも指定します。

thread_local

編集

thread_local を指定すると、その変数はスレッド固有の格納期間を持ちます。つまり、その変数の値はスレッド間で共有されません。

リンケージを指定するストレージクラス指定子

編集

リンケージを指定するストレージクラス指定子には、externstatic があります。

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_localstatic または extern と組み合わせて指定可能
    • autotypedef 以外のすべてと組み合わせて指定可能
    • constexprautoregisterstatic と組み合わせて指定可能
  • オブジェクトの宣言で 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 には多くの制約があり、注意が必要です。