yohhoyの日記

技術的メモをしていきたい日記

restrictキーワード

C99で追加された restrict キーワードについてのメモ。

コンパイラに対して「aliasが存在しないと仮定した最適化を許す」と伝えるためのキーワード。C99以降でのみ有効なキーワードであり、C++11現在でもC++には同キーワードが存在しない。(ただしコンパイラの独自拡張として、C++言語でもrestirctキーワードを使えるケースはある。)

要約:

  • 構文的にはconstやvolatileキーワードと同列で、ポインタ型に対してのみ型修飾を行える。int * restrictなど。restrict intint 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)の場合、コンパイラabが互いに異なるメモリ領域を指す前提で関数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

*1:int restrict * は「restrict修飾されたint型への(普通の)ポインタ型」となりNG。

*2:例えば「SIMD命令を用いて複数バイトをまとめてコピー」といった最適化を行える。

*3:restrict修飾ポインタが配列の要素を指している場合、同ポインタへの加減算により得られたポインタが指す要素も含めて互いにaliasとなってはいけない。言い換えれば、restrict修飾ポインタを介してアクセスするメモリ範囲が互いにオーバーラップしてはいけない。