restrict 型修飾子
C の型システムでは、個々の型には、それぞれ const 修飾子、 volatile 修飾子、 restrict 修飾子 (オブジェクトへのポインタ型の場合のみ) のうちの1つ、2つ、または3つ全部の組み合わせに対応する、その型の修飾されたバージョンがあります。 このページではそのうちの restrict 修飾子について説明します。
オブジェクト型へのポインタのみが restrict 修飾できます。 特に、以下は間違いです。
- int restrict *p
- float (* restrict f9)(void)
restrict の意味論は左辺値式にのみ適用されます。 例えば、 restrict 修飾されたポインタへのキャストや restrict 修飾されたポインタを返す関数呼び出しは、左辺値ではなく、その修飾には効果がありません。
restrict ポインタ P
が宣言されているブロック (一般的には P
を引数に取る関数の本体) の実行中に、 (直接または間接的に) P
を通してアクセス可能な何らかのオブジェクトが変更される場合、そのブロック内におけるそのオブジェクトへのすべてのアクセス (読み書き両方) は、必ず (直接または間接的に) P
を通して行わなければならず、そうでなければ動作は未定義です。
void f(int n, int * restrict p, int * restrict q) { while(n-- > 0) *p++ = *q++; // *p を通して変更されるどのオブジェクトも // *q を通して読み込まれるどのオブジェクトとも同じではありません。 // コンパイラは自由に最適化やベクトル化、ページマップなどができます。 } void g(void) { extern int d[100]; f(50, d + 50, d); // OK。 f(50, d + 1, d); // 未定義動作。 d[1]〜d[49] が f 内の p と q 両方からアクセスされます。 }
オブジェクトが決して変更されない場合は、エイリアスされていても良く、異なる restrict 修飾されたポインタを通してアクセスしても構いません (エイリアスされた restrict 修飾されたポインタの指す先のオブジェクトが同様にポインタである場合は、そのエイリアスは最適化を抑制します)。
ある restrict ポインタから別の restrict ポインタへの代入は、外側のブロック内のポインタから内側のブロック内のポインタへ代入するとき (restrict ポインタを引数に取る関数を呼ぶときに restrict ポインタを渡す場合も含みます) および関数から戻るとき (および直後に代入元ポインタのブロックが終了するとき) を除いて、未定義動作です。
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // 未定義動作。
restrict ポインタから restrict でないポインタへの代入は自由に行えます。 コンパイラがコードを解析可能である限り、最適化の機会はそのまま残されます。
void f(int n, float * restrict r, float * restrict s) { float * p = r, * q = s; // OK。 while(n-- > 0) *p++ = *q++; // ほとんど確実に *r++ = *s++ の場合と同様に最適化されます。 }
配列型が (typedef を用いて) restrict 型修飾子付きで宣言された場合は、その配列型ではなく、その要素型が restrict 修飾されます。
typedef int *array_t[10]; restrict array_t a; // a の型は int *restrict[10] です。
関数宣言において、配列型の関数引数を宣言するための角括弧内で、キーワード restrict
を使用できます。 これは、その配列型の変換後のポインタ型を修飾します。
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]); void g12(int n, float (*p)[n]) { f(10, n, p, p+10); // OK。 f(20, n, p, p+10); // 未定義動作の可能性があります (f が何を行うかによります)。 }
目次 |
[編集] ノート
restrict 修飾子の意図された用途は (register 記憶域クラスと同様に) 最適化を促進することです。 準拠しているプログラムのプリプロセッサ処理済みのすべての翻訳単位からすべての restrict 修飾子を削除しても、その意味 (すなわち観測可能な動作) は変わりません。
コンパイラは restrict
の使用によるエイリアシングの意味の一部または全部を自由に無視できます。
未定義動作を避けるためには、プログラマは restrict 修飾されたポインタによって行われるエイリアシングの表明に違反してはなりません。
多くのコンパイラは、言語の拡張として、 restrict
の逆 (たとえ型が異なっていてもポインタがエイリアスするかもしれないことを指定する属性) を提供しています (例: may_alias (gcc))。
[編集] 使用方法のパターン
restrict 修飾されたポインタの一般的な使用方法のパターンがいくつかあります。
[編集] ファイルスコープ
ファイルスコープの restrict 修飾されたポインタは、プログラムの実行中、単一の配列オブジェクトを指す必要があります。 その配列オブジェクトは、 restrict 修飾されたポインタと、その宣言された名前 (もしあれば) または別の restrict ポインタの、両方で参照されてはなりません。
ファイルスコープの restrict 修飾されたポインタは、動的確保されたグローバルな配列を提供するのに便利です。 restrict の意味論により、このポインタを通した参照を、静的な配列をその宣言された名前を通して参照する場合と、同様に最適化することが可能になります。
float * restrict a, * restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a は配列の前半を参照します。 b = t + n; // b は配列の後半を参照します。 } // コンパイラは、 restrict 修飾子から a, b, c の間に // 潜在的なエイリアシングがないことを推定できます。
[編集] 関数の引数
restrict 修飾されたポインタの最も人気のある使用方法は、関数の引数としての用途です。
以下の例では、コンパイラは変更されるオブジェクトにエイリアスがないと推論でき、そのためループを積極的に最適化できます。 f に入るとき、 restrict ポインタ a は、その紐付いた配列への排他的なアクセスを提供しなければなりません。 特に、 f 内において、 b と c はいずれも a に紐付いた配列またはその内部を指していてはなりません。
float x[100]; float *c; void f(int n, float * restrict a, float * const b) { int i; for ( i=0; i<n; i++ ) a[i] = b[i] + c[i]; } void g3(void) { float d[100], e[100]; c = x; f(100, d, e); // OK。 f( 50, d, d+50); // OK。 f( 99, d+1, d); // 未定義動作。 c = d; f( 99, d+1, e); // 未定義動作。 f( 99, e, d+1); // OK。 }
c が b に紐付いた配列を指すことは許されることに注意してください。 また、これらの目的に対しては、特定のポインタに紐付いた「配列」とは、配列オブジェクトのうち、そのポインタを通して実際に参照される部分のみを意味する、ということにも注意してください。
上記の例では、 b は const であるので関数の本体内で a への依存は発生し得ないため、 a と b がエイリアスすることはない、とコンパイラは推論できます。 また、プログラマは void f(int n, float * a, float const * restrict b) と書くこともできます。 この場合、 b を通して参照されるオブジェクトは変更できないため、変更されるオブジェクトが a と b の両方から参照されることはない、とコンパイラは推論できます。 プログラマは void f(int n, float * restrict a, float * b) と書きたいかもしれません。 この場合は、コンパイラは、関数の中身を調べない限りは、 a と b がエイリアスしないと推論することはできません。
一般的には、関数のプロトタイプ内のすべてのエイリアスしないポインタを、明示的に restrict で注釈するのか最良です。
[編集] ブロックスコープ
ブロックスコープの restrict 修飾されたポインタは、エイリアスの表明をそのブロックに限定します。 これにより、ローカルな表明を、タイトループのような重要なブロックのみに適用することが可能となります。 また、 restrict 修飾されたポインタを取る関数をマクロ化することも可能となります。
float x[100]; float *c; #define f3(N, A, B) \ { int n = (N); \ float * restrict a = (A); \ float * const b = (B); \ int i; \ for ( i=0; i<n; i++ ) \ a[i] = b[i] + c[i]; \ }
[編集] 構造体のメンバ
構造体のメンバである restrict 修飾されたポインタによって行われるエイリアスの表明のスコープは、その構造体をアクセスするために使用される識別子のスコープです。
たとえ構造体がファイルスコープで宣言されても、その構造体にアクセスするために使用される識別子がブロックスコープであれば、その構造体内のエイリアスの表明もブロックスコープです。 エイリアスの表明は特定のブロックの実行または特定の関数呼び出し (その構造体型のオブジェクトがどのように作成されたかによります) 内でのみ効果を持ちます。
struct t { // restrict ポインタは、それぞれのメンバが // 別々の記憶域を指すことを表明します。 float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r、 s、 u はブロックスコープです。 // r.p、 r.q、 s.p、 s.q、 u.p、 u.q は、すべて、 // ff の実行中は、別々の記憶域を指しているべきです。 // ... }
[編集] キーワード
[編集] 例
コード生成の例。 -S (gcc や clang の場合) または /FA (Visual Studio の場合) を付けてコンパイルしてください。
int foo(int *a, int *b) { *a = 5; *b = 6; return *a + *b; } int rfoo(int *restrict a, int *restrict b) { *a = 5; *b = 6; return *a + *b; }
出力例:
# 64ビット Intel プラットフォーム上で生成されたコード。 foo: movl $5, (%rdi) # *a に 5 を格納します。 movl $6, (%rsi) # *b に 6 を格納します。 movl (%rdi), %eax # *b への格納によって変更された場合のために *a を読み戻します。 addl $6, %eax # *a から読み込んだ値に 6 を加算します。 ret rfoo: movl $11, %eax # 結果は 11 です (コンパイル時定数)。 movl $5, (%rdi) # *a に 5 を格納します。 movl $6, (%rsi) # *b に 6 を格納します。 ret
[編集] 参考文献
- C11 standard (ISO/IEC 9899:2011):
- 6.7.3.1 Formal definition of restrict (p: 123-125)
- C99 standard (ISO/IEC 9899:1999):
- 6.7.3.1 Formal definition of restrict (p: 110-112)