変数指定子

C 言語では、変数を宣言するときに特定の指定子を追加して、変数の動作に関する追加情報をコンパイラーに提供できます。その主な役割は、コンパイラーによるコードの最適化を支援することであり、場合によってはプログラムの動作に影響を与えます。

定数

const 指定子は、変数が読み取り専用であり、変更してはならないことを示します。

const double PI = 3.14159;
PI = 3; // エラーを報告する

上記の例の「const」は、変数「PI」の値が変更されないことを意味します。変更すると、コンパイラはエラーを報告します。

配列の場合、「const」は配列メンバーを変更できないことを意味します。

const int arr[] = {1, 2, 3, 4};
arr[0] = 5; // エラーレポート

上記の例では、const により、配列 arr のメンバーが変更不可能になります。

ポインタ変数の場合、「const」の書き方は 2 通りあり、それぞれ意味が異なります。 const* の前にある場合、ポインタが指す値は変更できないことを意味します。

// const は、*x が指す値は変更できないことを意味します
int const * x
// または
const int * x

次の例では、「x」が指す値を変更するとエラーが発生します。

int p = 1
const int* x = &p;

(*x)++; // エラーを報告する

const* の後に続く場合、ポインタに含まれるアドレスは変更できないことを意味します。

// const はアドレス x が変更できないことを意味します
int* 定数 x

次の例では、「x」を変更するとエラーが発生します。

int p = 1
int* const x = &p;

x++; // エラーレポート

この 2 つは組み合わせることができます。

const char* const x;

上の例では、ポインター変数 x は文字列を指します。 2 つの const は、x に含まれるメモリ アドレスも x が指す文字列も変更できないことを意味します。

const の使用法の 1 つは、関数本体内で関数パラメーターが変更されるのを防ぐことです。関数本体内でパラメーターを変更しない場合は、関数を宣言するときにパラメーターに const 指定子を追加できます。この場合、この関数を使用する人は、プロトタイプ内の const を見ると、関数の呼び出しの前後でパラメータの配列が変化していないことがわかります。

void find(const int* arr, int n);

上記の例では、関数 find のパラメータ配列 arrconst 指定子が付いています。これは、配列が関数内で変更されないことを意味します。

注意すべき点の 1 つは、ポインタ変数が const 変数を指している場合、そのポインタ変数を変更すべきではないということです。

const int i = 1;
int* j = &i;
*j = 2; // エラーレポート

上の例では、「j」は変数「i」を指すポインタ変数です。つまり、「j」と「i」は同じアドレスを指します。 j 自体には const 指定子がありませんが、i にはあります。この場合、「j」が指す値も変更できません。

静的

static 指定子は、グローバル変数とローカル変数では異なる意味を持ちます。

(1) ローカル変数の場合 (ブロック スコープ内にあります)。

関数内で宣言されたローカル変数に static が使用される場合、変数の値は関数の各実行後に保持され、次回の実行時には初期化されないことを意味します。これは、グローバル変数と同様です。関数内でのみ使用されます。関数を実行するたびに変数を初期化する必要がないため、関数の実行速度が向上します。詳細は「関数」の章を参照してください。

(2) グローバル変数の場合 (ブロックのスコープ外にある)。

関数の外で宣言されたグローバル変数に static を使用した場合、その変数は現在のファイル内でのみ使用され、他のソース コード ファイルからはその変数を参照できません。つまり、変数はリンクされません。

static で変更された変数を初期化する場合、値は変数と同じであってはならず、定数でなければなりません。

int n = 10;
static m = n; // エラーを報告します。

上の例では、変数 mstatic 変更が加えられており、その値が変数 n と等しい場合、エラーが報告され、定数と等しくなければなりません。

現在のファイルでのみ使用される関数は、その関数が現在のファイルでのみ使用され、他のファイルでも同じ名前の関数を定義できることを示す「static」として宣言することもできます。

静的 int g(int i);

自動

auto 指定子は、この変数の格納場所を示します。コンパイラは、メモリ空間を独立して割り当て、それが定義されているスコープ内にのみ存在し、スコープを出るときに自動的に解放されます。

デフォルトの動作である extern 変数 (外部変数) でない限り、コンパイラは独自にメモリ空間を割り当てるため、この指定子は実際的な効果はなく、通常は省略されます。

自動整数;
// と同等
int a;

外部

extern 指定子は、変数が別のファイルで宣言されており、現在のファイルでその変数にスペースを割り当てる必要がないことを示します。通常、変数が複数のファイルで共有されていることを示すために使用されます。

外部整数;

上記のコードでは、「a」は「extern」変数です。これは、変数が他のファイルで定義および初期化されており、現在のファイルがその変数に記憶領域を割り当てる必要がないことを意味します。

ただし、変数の宣言と初期化を同時に行った場合、externは無効となります。

// 外部が無効です
extern int i = 0;

// と同等
int i = 0;

上記コードでは、externによる変数の初期化宣言が無効です。これは、同じ変数の複数の extern 初期化を防ぐためです。

extern を使用して関数内で変数を宣言することは、変数を静的に保存することと同等であり、その値は実行のたびに外部から取得する必要があります。

関数自体はデフォルトで extern です。つまり、関数は外部ファイルによって共有できます。通常、extern は省略され、記述されません。現在のファイルでのみ関数を使用できるようにしたい場合は、関数の前に static を追加する必要があります。

extern int f(int i);
// と同等
int f(int i);

登録する

register 指定子は、変数が頻繁に使用され、最速の読み取り速度を提供する必要があるため、レジスタに配置する必要があることをコンパイラに示します。ただし、コンパイラはこの指定子を無視することができ、必ずしもこの指示に従う必要はありません。

int を登録します。

上記の例では、register は、変数 a が頻繁に使用され、最速の読み取り速度を提供する必要があることをコンパイラーに指示します。

register は、コード ブロック内で宣言された変数に対してのみ有効です。

registerに設定した変数のアドレスが取得できません。

int を登録します。
int *p = &a; // コンパイラエラー

上記の例では、変数 'a' がレジスタに配置されている可能性があり、メモリ アドレスを取得できないため、'&a' はエラーを報告します。

配列が「register」に設定されている場合、配列全体または配列メンバーのアドレスを取得できません。

レジスタ int a[] = {11, 22, 33, 44, 55};

int p = a; // エラー
int a = *(a + 2); // エラー

歴史的には、CPU 内のキャッシュはレジスタと呼ばれていました。レジスタはメモリよりもアクセスがはるかに速いため、レジスタを使用すると速度が向上します。しかし、これらはメモリ内にないため、メモリ アドレスを持たず、そのため、それらへのポインタのアドレスを取得できません。最新のコンパイラは大幅な進歩を遂げており、可能な限りコードを最適化し、独自のルールに従ってレジスタを有効に活用する方法を決定して、最高の実行速度を実現します。そのため、コード内の「register」指定子を無視する場合があります。これらの変数がレジスタに配置されるという保証はありません。

揮発性

volatile 指定子は、宣言された変数が予期せず変更される可能性があり (つまり、他のプログラムがその値を変更する可能性がある)、現在のプログラムによって制御されないことを示します。したがって、コンパイラーはそのような変数が使用されるたびにその値を最適化すべきではありません。毎回問い合わせる必要があります。この指定子は、ハードウェア デバイスのプログラミングでよく使用されます。

volatile int foo;
揮発性 int* バー;

「volatile」の目的は、コンパイラーが変数の動作を最適化するのを防ぐことです。以下の例を参照してください。

int foo = x;
//その他のステートメントでは、x の値が変更されていないと仮定します。
int バー = x;

上記のコードでは、変数 foobar は両方とも x に等しく、x の値は変更されていないため、コンパイラは x をキャッシュに置き、キャッシュから直接値を読み取る可能性があります。 (x の元のメモリ位置から読み取る代わりに) キャッシュを作成し、値を foobar に割り当てます。 xvolatile に設定されている場合、コンパイラはそれをキャッシュに入れず、毎回元の場所から x の値をフェッチしません。これは、読み取りの間に他のプログラムが x を変更する可能性があるためです。

制限

restrict 指定子を使用すると、コンパイラは特定のコードを最適化できます。これはポインターでのみ使用でき、ポインターがデータにアクセスする唯一の方法であることを示します。

int* 制限 pt = (int*) malloc(10 * sizeof(int));

上記の例では、restrict は変数 pt が malloc によって割り当てられたメモリにアクセスする唯一の方法であることを意味します。

次の例の変数 foo では、restrict 修飾子を使用できません。

int foo[10];
int* バー = foo;

上記の例では、変数 foo が指すメモリには foo または bar を使用してアクセスできるため、 foo を制限に設定することはできません。

メモリの特定のブロックが一方向にのみアクセスできることをコンパイラが認識していれば、値が他の場所で変更されることを心配する必要がないため、コードをより適切に最適化できる可能性があります。

関数パラメータに「restrict」が使用される場合、パラメータのメモリアドレス間に重複がないことを意味します。

void swap(int* 制限 a, int* 制限 b) {
  int t;
  t = *a;
  *a = *b;
  *b = t;
}

上記の例では、関数のパラメータ宣言の「restrict」は、パラメータ「a」とパラメータ「b」のメモリアドレスが重複しないことを意味します。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0