C Language
ストレージクラス
サーチ…
前書き
記憶域クラスは、変数または関数のスコープを設定するために使用されます。変数のストレージクラスを知ることで、プログラムの実行時にその変数の存続時間を判断できます。
構文
[auto | register | static | extern] <データ型> <変数名> [= <値>];
[静的_Thread_local | extern _Thread_local | _Thread_local] <データ型> <変数名> [= <値>]; / *>以降= C11 * /
例:
typedef int foo ;
extern int foo [2];
備考
ストレージクラス指定子は、宣言のトップレベル型の隣に表示されるキーワードです。これらのキーワードを使用すると、ファイルスコープで宣言されているかブロックスコープで宣言されているかによって、宣言されたオブジェクトの格納期間とリンケージに影響します。
キーワード | 保管期間 | リンケージ | 備考 |
---|---|---|---|
static | 静的 | 内部 | ファイルスコープのオブジェクトの内部リンケージを設定します。ブロックスコープのオブジェクトの静的記憶期間を設定します。 |
extern | 静的 | 外部 | したがって、初期化子を持つファイルスコープで定義されたオブジェクトには暗黙のため、重複しています。イニシャライザなしでファイルスコープの宣言で使用される場合、その定義が別の変換単位で検出され、リンク時に解決されることを暗示します。 |
auto | 自動 | 無関係 | ブロックスコープで宣言されたオブジェクトには暗示され、したがって冗長性があります。 |
register | 自動 | 無関係 | 自動保存期間を持つオブジェクトにのみ関連します。変数がレジスタに格納されるべきであるというヒントを提供します。課される制約は1つが単項使用できないことがある& 、そのような物体上のオペレータ「のアドレス」、したがってオブジェクトがエイリアスすることができません。 |
typedef | 無関係 | 無関係 | 実際にはストレージクラス指定子ではありませんが、構文的な観点からは同じように動作します。唯一の違いは、宣言された識別子がオブジェクトではなく型であることです。 |
_Thread_local | 糸 | 内部/外部 | C11で導入され、 スレッド記憶期間を表します 。ブロックスコープで使用される場合は、 extern またはstatic も含みstatic 。 |
これらのキーワードが省略されている場合でも、すべてのオブジェクトには関連する保存期間(スコープに関係なく)とリンケージ(ファイルスコープでの宣言にのみ関連する)があります。
トップレベル型指定子( int
、 unsigned
、 short
など)およびトップレベル型修飾子( const
、 volatile
)に対するストレージクラス指定子の順序は強制されないため、これらの宣言は両方とも有効です。
int static const unsigned a = 5; /* bad practice */
static const unsigned int b = 5; /* good practice */
しかし、最初に記憶域クラス指定子を置いてから型修飾子を指定し、型指定子( void
、 char
、 int
、 signed long
、 unsigned long long
、 long double
...)を置くことをおchar
ます。
すべてのストレージクラス指定子が特定のスコープで合法であるとは限りません。
register int x; /* legal at block scope, illegal at file scope */
auto int y; /* same */
static int z; /* legal at both file and block scope */
extern int a; /* same */
extern int b = 5; /* legal and redundant at file scope, illegal at block scope */
/* legal because typedef is treated like a storage class specifier syntactically */
int typedef new_type_name;
保管期間
保存期間は静的または自動のいずれかです。宣言されたオブジェクトの場合、そのスコープとストレージクラス指定子によって決定されます。
静的ストレージ期間
静的な記憶期間を持つ変数は、プログラムの実行全体を通して生きており、ファイルスコープ( static
または無し)とブロックスコープ(明示的にstatic
に置く)の両方で宣言することができます。これらは通常、プログラムの起動時にオペレーティングシステムによって割り当てられ、初期化され、プロセスが終了すると再利用されます。実際には、実行形式にはそのような変数( data
、 bss
、およびrodata
)の専用セクションがあり、ファイルのこれらのセクション全体が特定の範囲でメモリにマップされます。
スレッドの保存期間
この保管期間はC11で導入されました。これは以前のC標準では利用できませんでした。コンパイラの中には、同様のセマンティクスを持つ非標準拡張を提供するものがあります。例えば、gccがサポート__thread
持っていなかった以前のC規格で使用可能な指定_Thread_local
。
スレッド記憶期間を持つ変数は、ファイルスコープとブロックスコープの両方で宣言できます。ブロックスコープで宣言されている場合は、 static
またはextern
ストレージ指定子も使用します。その存続期間は、それが作成されたスレッドの実行全体です。これは、別のストレージ指定子と一緒に使用できる唯一のストレージ指定子です。
自動保存期間
自動保存期間を持つ変数は、ブロックスコープで(関数内またはその関数内のブロック内で直接)宣言することができます。これらは、ファンクションまたはブロックの入力から終了までの間にのみ使用できます。変数がスコープから外れると(関数から戻るかブロックを離れることによって)、そのストレージは自動的に割り当てが解除されます。ポインタからの同じ変数へのそれ以上の参照は無効であり、未定義の動作につながります。
典型的な実装では、自動変数は、関数のスタックフレームまたはレジスタ内の特定のオフセットに配置されます。
外部リンクと内部リンク
リンケージはファイルスコープで宣言されたオブジェクト(関数と変数)にのみ関連し、異なる翻訳単位間の可視性に影響します。外部リンケージを持つオブジェクトは、他のすべての翻訳単位で表示されます(適切な宣言が含まれている場合)。内部リンケージを持つオブジェクトは他の翻訳単位には公開されず、定義されている翻訳単位でのみ使用できます。
typedef
既存の型に基づいて新しい型を定義します。その構文は、変数宣言のそれを反映しています。
/* Byte can be used wherever `unsigned char` is needed */
typedef unsigned char Byte;
/* Integer is the type used to declare an array consisting of a single int */
typedef int Integer[1];
/* NodeRef is a type used for pointers to a structure type with the tag "node" */
typedef struct node *NodeRef;
/* SigHandler is the function pointer type that gets passed to the signal function. */
typedef void (*SigHandler)(int);
技術的にはストレージクラスではありませんが、 typedef
キーワードが使用されている場合、他のストレージクラスは許可されていないため、コンパイラはこれを1とtypedef
。
typedef
は重要であり、 #define
マクロで置き換えるべきではありません。
typedef int newType;
newType *ptr; // ptr is pointer to variable of type 'newType' aka int
しかしながら、
#define int newType
newType *ptr; // Even though macros are exact replacements to words, this doesn't result to a pointer to variable of type 'newType' aka int
オート
この記憶クラスは、識別子が自動記憶期間を有することを示す。つまり、識別子が定義されたスコープが終了すると、識別子で示されるオブジェクトはもはや有効ではなくなります。
グローバルスコープやstatic
宣言static
れていないすべてのオブジェクトは、定義時にデフォルトで自動保存期間があるため、このキーワードは主に歴史的関心事であり、使用しないでください。
int foo(void)
{
/* An integer with automatic storage duration. */
auto int i = 3;
/* Same */
int j = 5;
return 0;
} /* The values of i and j are no longer able to be used. */
静的
static
ストレージ・クラスは、ファイル内の宣言の場所によって異なる目的を果たします。
識別子をその翻訳単位のみに限定するには(scope = file)。
/* No other translation unit can use this variable. */ static int i; /* Same; static is attached to the function type of f, not the return type int. */ static int f(int n);
関数の次の呼び出しで使用するためにデータを保存するには(scope = block):
void foo() { static int a = 0; /* has static storage duration and its lifetime is the * entire execution of the program; initialized to 0 on * first function call */ int b = 0; /* b has block scope and has automatic storage duration and * only "exists" within function */ a += 10; b += 10; printf("static int a = %d, int b = %d\n", a, b); } int main(void) { int i; for (i = 0; i < 5; i++) { foo(); } return 0; }
このコードは以下を出力します:
static int a = 10, int b = 10 static int a = 20, int b = 10 static int a = 30, int b = 10 static int a = 40, int b = 10 static int a = 50, int b = 10
静的変数は、複数の異なるスレッドから呼び出された場合でもその値を保持します。
配列を示すために関数のパラメータで使用される要素は、最小限の要素数と非nullパラメータが一定であることが期待されます。
/* a is expected to have at least 512 elements. */ void printInts(int a[static 512]) { size_t i; for (i = 0; i < 512; ++i) printf("%d\n", a[i]); }
必要な項目数(またはnull以外のポインタさえも)は、コンパイラによって必ずしもチェックされるわけではなく、十分な要素がない場合、コンパイラは何らかの方法で通知する必要はありません。プログラマが512個以下の要素またはヌルポインタを渡す場合、未定義の動作が結果になります。これを強制することは不可能なので、そのような関数にそのパラメーターの値を渡すときは、特別な注意が必要です。
extern
他の場所で定義されている(そして外部のリンケージを持つ) オブジェクトまたは関数を宣言するために使用されます。一般に、対応するオブジェクトまたは関数が定義されていないモジュールで使用されるオブジェクトまたは関数を宣言するために使用されます。
/* file1.c */
int foo = 2; /* Has external linkage since it is declared at file scope. */
/* file2.c */
#include <stdio.h>
int main(void)
{
/* `extern` keyword refers to external definition of `foo`. */
extern int foo;
printf("%d\n", foo);
return 0;
}
C99のinline
キーワードの導入により、少し面白いことが起こります。
/* Should usually be place in a header file such that all users see the definition */
/* Hints to the compiler that the function `bar` might be inlined */
/* and suppresses the generation of an external symbol, unless stated otherwise. */
inline void bar(int drink)
{
printf("You ordered drink no.%d\n", drink);
}
/* To be found in just one .c file.
Creates an external function definition of `bar` for use by other files.
The compiler is allowed to choose between the inline version and the external
definition when `bar` is called. Without this line, `bar` would only be an inline
function, and other files would not be able to call it. */
extern void bar(int);
登録
オブジェクトへのアクセスは可能な限り高速でなければならないことをコンパイラに知らせる。コンパイラが実際にヒントを使用するかどうかは、実装定義です。単にそれをauto
と同等として扱うかもしれません。
register
で宣言register
ているすべてのオブジェクトでは、唯一異なるプロパティがあるため、アドレス計算はできません。これにより、 register
は特定の最適化を保証するための優れたツールになります。
register size_t size = 467;
予期せず変更される可能性のある別の関数にコードを渡すことができないため、 エイリアスにならないオブジェクトです。
このプロパティは、配列
register int array[5];
その最初の要素へのポインタには崩壊できません( array
&array[0]
変わり&array[0]
)。これは、そのような配列の要素にアクセスすることができず、配列自体を関数に渡すことができないことを意味します。
実際、 register
記憶クラスで宣言された配列の唯一の正当な使用法は、 sizeof
演算子です。他の演算子は配列の最初の要素のアドレスを必要とします。そのため、配列全体のサイズ計算以外は役に立たないので、 register
キーワードで宣言するのは一般的ではありません。これはregister
キーワードなしでも簡単に行うことができます。
register
記憶クラスは、ブロック内で定義され、高い頻度でアクセスされる変数に適しています。例えば、
/* prints the sum of the first 5 integers*/
/* code assumed to be part of a function body*/
{
register int k, sum;
for(k = 1, sum = 0; k < 6; sum += k, k++);
printf("\t%d\n",sum);
}
_Alignof
演算子は、 register
配列でも使用できます。
_Thread_local
これはC11で導入された新しい記憶域指定子で、マルチスレッド化されました。これは以前のC標準では利用できません。
スレッドの保存期間を示します。 _Thread_local
記憶域指定子で宣言された変数は、そのオブジェクトがそのスレッドに対してローカルであり、その存続期間が、そのスレッドが作成されたスレッドの実行全体であることを示します。 static
またはextern
と一緒に表示することもできます。
#include <threads.h>
#include <stdio.h>
#define SIZE 5
int thread_func(void *id)
{
/* thread local variable i. */
static _Thread_local int i;
/* Prints the ID passed from main() and the address of the i.
* Running this program will print different addresses for i, showing
* that they are all distinct objects. */
printf("From thread:[%d], Address of i (thread local): %p\n", *(int*)id, (void*)&i);
return 0;
}
int main(void)
{
thrd_t id[SIZE];
int arr[SIZE] = {1, 2, 3, 4, 5};
/* create 5 threads. */
for(int i = 0; i < SIZE; i++) {
thrd_create(&id[i], thread_func, &arr[i]);
}
/* wait for threads to complete. */
for(int i = 0; i < SIZE; i++) {
thrd_join(id[i], NULL);
}
}