Oracle® Developer Studio 12.5: C ユーザーズガイド

印刷ビューの終了

更新: 2016 年 7 月
 
 

7.5 constvolatile

キーワード const は、ISO C に含められた C++ 機能の 1 つでした。ISO C 委員会によって類似のキーワード volatile が考案された際に、「型修飾子」というカテゴリが作成されました。

7.5.1 lvalue 専用の型

constvolatile は識別子の型の一部であり、記憶クラスの一部ではありません。ただし、それらは多くの場合、式の評価中にオブジェクトの値が取り出されるとき (正確には、lvaluervalue になるとき) に、型のいちばん上の部分から削除されます。これらの用語は、プロトタイプ代入式 left-hand-side=right-hand-side; から来ています。ここで、左側は依然としてオブジェクトを直接参照している必要があり (lvalue)、右側は値であるだけでよい (rvalue) ということです。したがって、lvalues である式だけが const または volatile (あるいは、その両方) で修飾できます。

7.5.2 派生型の型修飾子

型修飾子は型名と派生型を変更します。派生型は C の宣言の一部であり、何度も適用することによって、より複雑な型 (ポインタ、配列、関数、構造体、共用体) を構築できます。関数を除き、1 つまたは両方の型修飾子を使用すると、派生型の動作を変更できます。

この例は、型が const int であり、値が正しいプログラムによって変更されないオブジェクトを宣言し、初期化します。

const int five = 5;

キーワードの順番は C にとって重要ではありません。たとえば、次の宣言は、最初の例と効果の点で同じです。

int const five = 5;
const five = 5;

次の宣言は、以前に宣言したオブジェクトを初期状態で指す、const int 型のポインタのオブジェクトを宣言します。

const int *pci = &five;

このポインタ自身は修飾型を持ちませんが、そのポイント先が修飾型になっています。そのポイント先はプログラムの実行中、基本的に任意の int に変更可能です。pci を使用してそのポイント先のオブジェクトを変更することはできません。ただし、次の例のようにキャストを使用すれば可能です。

*(int *)pci = 17;

pci が実際に const オブジェクトを指す場合、このコードの動作は未定義です。

次の宣言は、int への const ポインタという型を持つ大域オブジェクトの定義が、プログラム内のどこかに存在することを示しています。

extern int *const cpi;

この場合、正しいプログラムでは cpi の値は変更されません。しかし、cpi を使用して、cpi が指すオブジェクトを変更することはできます。この宣言において、const* のあとにあることに注意してください。次の 2 つの宣言の効果は同じです。

typedef int *INT_PTR;
extern const INT_PTR cpi;

前述の宣言は、次の宣言のように連結できます。この場合、オブジェクトの型は const int への const ポインタであると宣言されます。

const int *const cpci;
  

7.5.3 constreadonly を意味する

なお、キーワードとしては通常 const よりも readonly を選択するほうが便利です。この方法で const を読み取ると、次の例のような宣言は、2 番目のパラメータが文字値を読み取るためにのみ使用されるのに対して、最初のパラメータはそれが指している文字を上書きすることを示していることがすぐわかります。

char *strcpy(char *, const char *);

さらに、この例で cpi の型が const int へのポインタであるという事実にかかわらず、実際に型が const int で宣言されたオブジェクトを指していないかぎり、ポイント先のオブジェクトの値は別の方法で変更できます。

7.5.4 const の使用例

const の 2 つの主な使用法は、コンパイル時に初期化された大きな情報テーブルが未変更であると宣言することと、ポインタパラメータが指しているオブジェクトを変更しないことを指定することです。

最初の使用法では、同じプログラムのほかの並行呼び出しが、プログラムのデータ部分を共有可能にします。データはメモリーの読み取り専用部分にあるため、この不変データを変更しようとする試みを、ある種類のメモリー保護障害で即座に検出できます。

const の 2 番目の使用法は、メモリーで障害が発生する前に潜在的なエラーを特定するのに役立ちます。たとえば、ヌル文字を挿入できない文字列に対して、ある関数が一時的にヌル文字を挿入しようとした場合、その関数は、コンパイル時、ヌル文字を挿入できない文字列へのポインタが渡されたときに検出されます。

7.5.5 volatile の使用例

これまでの例では、const は概念的に単純なものとして示されてきました。しかし、volatile はどのような意味でしょうか。それはコンパイラにとって、そのようなオブジェクトへのアクセス時にコード生成上のショートカットを行ってはいけない、ということを意味します。一方、ISO C では、対応する特殊な特性を持つすべてのオブジェクトを volatile として宣言することはプログラマの責任としています。

volatile は、通常、次の 4 つのオブジェクトに使用します。

  • メモリーにマップされた入出力ポートであるオブジェクト

  • 複数の並行プロセス間で共有されるオブジェクト

  • 非同期シグナルハンドラによって変更されるオブジェクト

  • setjmp を呼び出す関数中で宣言され、その値が setjmp への呼び出しとそれに対応する longjmp への呼び出し間で変更される自動記憶オブジェクト

最初の 3 つの例はすべて、特定の動作を行うオブジェクトのインスタンスです。つまり、その値は、プログラムの実行中の任意の時点で変更できます。したがって、一見無限ループに見える次のループは、flagvolatile で修飾された型を持つかぎりにおいて有効となります。

flag = 1;
while (flag);

おそらく、ある非同期イベントが将来 flag をゼロに設定することもあります。volatile 修飾型を持たない場合、flag の値はループ本体内では変更されないため、コンパイルシステムによって前述のループは、完全に flag の値を無視する本当の無限ループに変更されることもあり得ます。

4 番目の例は、setjmp を呼び出す関数に対して局所的な変数を含んでいるため、より複雑です。setjmplongjmp の動作に関する詳細は、4 番目の例に一致するオブジェクトの値は予測不可能であることを示します。もっとも望ましい動作を行うためには、longjmp が、setjmp を呼び出す関数と longjmp を呼び出す関数の間で、保存されたレジスタ値に対してすべてのスタックフレームを検査する必要があります。スタックフレームは非同期的に作成される可能性があるため、この作業はより難しくなります。

自動オブジェクトが volatile 修飾型で宣言される場合、コンパイラは、プログラマが書いたものと完全に一致するコードを生成する必要があります。したがって、このような自動オブジェクトに対する最新の値は、レジスタ内だけでなく常にメモリー内にあり、longjmp が呼び出されたときに最新であることが保証されます。