本物のC

C の知識:型の修飾


C 言語では、型を以下のキーワードによって修飾する(qualify、modify)事ができます。

const

constで修飾された型の値は定数(constant)、つまり変更できません。

例えばconst intとして宣言された変数は、初期化以外で代入を試みるとコンパイルに失敗します。(強制的にintへキャストして書き込もうとした場合は実行時エラーになります6.7.3#5。)

ポインタ型について

ポインタ型の場合、ポインタの指す先とポインタ自身の領域それぞれにconstを付加する事ができます。

例えばconst int *は「const intへのポインタ」になる一方、int * constは「intへの定数ポインタ」になります。(「C の基本:ポインタ」で言った「*は左矢印 ← みたいなもの」を思い出すとよいでしょう。)

int a;
const int *x;
int * const y = &a;
x = &a; /* 有効 */
*x = 1; /* エラー */
y = &a; /* エラー */
*y = 1; /* 有効 */

また例えばint * const *x;は、x*x**xの内で*xのみ書き込み不能になります。

volatile

volatile=気紛れな、移ろいやすいの言葉通り、volatileで修飾された型については特にアクセスしなくとも変更される可能性が考慮されます。

典型的には、

を扱う場合に必要となります。

xが通常のchar型である場合、その値はプログラム自身が変更しない限り一定なので、

y = x + 1;
z = x + 2;

といったコードはxのメモリ領域を一度だけ読み込む事で実行できます。つまりコンパイラはこれを効率的に実行する為に、

  1. メモリ上のxの値を AL レジスタに記憶する
  2. AL レジスタの値に 1 足してyへ書き込む
  3. AL レジスタの値に 2 足してzへ書き込む

といった機械語を生成する可能性があります。

しかしxが何らかのデバイス、例えば光センサーへとハードウェア的に結び付けられていた場合、xは読み込む度に異なる値を返す事になるので、レジスタに記憶した値を使い回すべきではありません。

こうした場合にxvolatile charとして宣言すれば、上の様なコンパイラの最適化を防ぎ書いた通りに実行させる事ができます6.7.3#5

restrict

restrictは C99 で新たに追加されたキーワードで、ポインタ型のみを修飾し「他のポインタからはアクセスされない」事を表します。

例えば標準ライブラリのstring.hにはmemcpyという関数がありますが、これは

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

と宣言されています。

s1s2restrictという事は、この関数は「s1s2が重複する領域を指さない」と考えて実装されている事を意味し、呼び出し側もそれを守る必要があります。

従来ではこの様な条件はコメントに書いたりするしかありませんでしたが、restrictによってコードで明示する事が可能になりました。これは「可読性」の向上だけでなく、コンパイラの最適化にも貢献します。