C99で追加された restrict キーワードについてのメモ。
コンパイラに対して「aliasが存在しないと仮定した最適化を許す」と伝えるためのキーワード。C99以降でのみ有効なキーワードであり、C++11現在でもC++には同キーワードが存在しない。(ただしコンパイラの独自拡張として、C++言語でもrestirctキーワードを使えるケースはある。)
要約:
- 構文的にはconstやvolatileキーワードと同列で、ポインタ型に対してのみ型修飾を行える。
int * restrict
など。restrict int
やint restrict *
はill-formed*1。 - コンパイラでの最適化を助けるためのもの。restrictキーワードを削除してもプログラムの意味は変化しない。
用法と意味
restrictキーワードの用法は、C標準ライブラリmemcpy
関数/memmove
関数における動作仕様と関数宣言の違いが理解しやすい。C99では両関数は次の通り定義されている。
void *memcpy(void * restrict s1, const void * restrict s2, size_t n); void *memmove(void *s1, const void *s2, size_t n);
memcpy
関数のように “コピー元とコピー先範囲が重複してはいけない” といった外部仕様を持つ関数では、そのポインタ型引数にrestrictキーワードを付けることができる。一方のmemmove
関数は、コピー元/先範囲が重複していても正常動作を保証する。こちらはポインタ型引数にrestrictキーワードが付与されていない。
restrictキーワードの存在により、memcpy
関数実装のコンパイル時に “コピー元/先範囲が重複しないこと” を前提とした積極的な最適化が可能となる*2。その代わり、関数呼び出し側が “コピー元/先範囲が重複しないこと” を保証しなければならない。違反した場合は未定義動作(undefined behavior)を引き起こす。
なお「異なる型へのポインタ型同士」の場合は、restrictキーワードによる指示がなくてもstrict aliasing ruleに基づいて、コンパイラは互いに他方のaliasにならないと仮定した最適化を行う。このstrict aliasing ruleについてはUnderstanding C/C++ Strict Aliasing(同記事の拙訳)が詳しい。例えば、関数void foo(int *a, short *b)
の場合、コンパイラはa
とb
が互いに異なるメモリ領域を指す前提で関数fooの最適化を行ってよい。このため、1個のint型変数x
を使ってfoo(&x, (short*)&x);
のように呼ぶと未定義動作となる。
restrict修飾子
JTC1/SC22/WG14 N1570では、下記の通り定義される。
- restrict修飾されたポインタ型が直接的または間接的*3に指す先について、同一関数/ブロック内の別ポインタが指さない(aliasが存在しない)ことをコンパイラに伝える。このルールを破るプログラムは未定義動作となる。(6.7.3/8, 6.7.3.1/4)
- 文法要素としてはconstやvolatileと同じく型修飾子type-qualifierに属する。(6.2.5/26, 6.7.3/1)
- ポインタ型に対してのみ修飾できる。(6.7.3/2)
- コンパイラでの最適化を補助するために存在しており、restrictキーワードを全て削除してもプログラムの意味は変化しない。(6.7.3/8, 6.7.3.1/6)
→ 続 restrictキーワード に続く。
関連URL