restrict 型修飾子 (C99 以降)
C の 型システムにおける個々の型には、それぞれいくつかの「修飾された」バージョンがあります。これは、const、volatile、そしてオブジェクト型へのポインタの場合は restrict 修飾子のいずれか 1 つ、2 つ、または 3 つすべてに対応します。このページでは、restrict 修飾子の効果について説明します。
restrict 修飾できるのは、オブジェクト型へのポインタ またはその(多次元である可能性のある)配列(C23 以降) のみです。特に、以下は *エラー* となります。
- int restrict *p
- float (* restrict f9)(void)
Restrict セマンティクスは左辺値式にのみ適用されます。たとえば、restrict 修飾されたポインタへのキャストや、restrict 修飾されたポインタを返す関数呼び出しは左辺値ではないため、修飾子は効果がありません。
制限されたポインタ P が宣言されているブロック(通常は P が関数パラメータである関数の本体の各実行)が実行されている間、P を通じて(直接または間接的に)アクセス可能なオブジェクトが何らかの方法で変更された場合、そのブロック内でのそのオブジェクトへのすべてのアクセス(読み取りと書き込みの両方)は、P を通じて(直接または間接的に)行われなければなりません。それ以外の場合は、未定義の動作となります。
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; // none of the objects modified through *p is the same // as any of the objects read through *q // compiler free to optimize, vectorize, page map, etc. } void g(void) { extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Undefined behavior: d[1] is accessed through both p and q in f }
オブジェクトが一度も変更されない場合、エイリアス化され、異なる restrict 修飾されたポインタを通じてアクセスされる可能性があります。(エイリアス化された restrict 修飾されたポインタが指すオブジェクト自体がポインタである場合、このエイリアス化は最適化を妨げる可能性があることに注意してください。)
制限されたポインタから別の制限されたポインタへの代入は未定義の動作です。ただし、ある外部ブロックのオブジェクトへのポインタから、ある内部ブロックのポインタへの代入(制限されたポインタ引数を使用して、制限されたポインタパラメータを持つ関数を呼び出す場合を含む)や、関数から戻る場合(それ以外の場合は、from-pointer のブロックが終了した場合)は除きます。
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // undefined behavior
制限されたポインタは、非制限ポインタに自由に代入できます。コンパイラがコードを分析できる限り、最適化の機会は維持されます。
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // OK while (n-- > 0) *p++ = *q++; // almost certainly optimized just like *r++ = *s++ }
|
配列型が restrict 型修飾子で宣言された場合(typedef を使用して)、配列型自体は restrict 修飾されていませんが、その要素型は restrict 修飾されます。 |
(C23まで) |
|
配列型とその要素型は、常に同じように restrict 修飾されているとみなされます。 |
(C23以降) |
typedef int *array_t[10]; restrict array_t a; // the type of a is int *restrict[10] // Notes: clang and icc reject this on the grounds that array_t is not a pointer type void *unqual_ptr = &a; // OK until C23; error since C23 // Notes: clang applies the rule in C++/C23 even in C89-C17 modes
関数宣言において、キーワード 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); // possibly undefined behavior (depending on what f does) }
目次 |
[編集] 注記
restrict 修飾子(register ストレージクラスと同様)の意図された使用法は、最適化を促進することであり、適合プログラムを構成するすべてのプリプロセッサ翻訳単位から修飾子のすべてのインスタンスを削除しても、その意味(つまり、観測可能な動作)は変更されません。
コンパイラは、restrict の使用におけるエイリアス化の含意を、すべて、または一部を無視しても構いません。
未定義の動作を回避するために、プログラマは restrict 修飾されたポインタによって行われたエイリアス化の主張が侵害されていないことを保証する必要があります。
多くのコンパイラは、言語拡張として、restrict の反対、つまり、ポインタは型が異なってもエイリアス化する可能性があることを示す属性を提供します: may_alias (gcc)。
[編集] 使用パターン
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 refers to 1st half b = t + n; // b refers to 2nd half } // compiler can deduce from the restrict qualifiers that // there is no potential aliasing among the names a, b, and c
[編集] 関数パラメータ
restrict 修飾されたポインタの最も一般的なユースケースは、関数パラメータとしての使用です。
以下の例では、コンパイラは変更されたオブジェクトのエイリアス化がないと推論できるため、ループを積極的に最適化できます。f へのエントリ時に、制限されたポインタ a はその関連配列への排他的アクセスを提供する必要があります。特に、f 内では、b も c も a に関連付けられた配列を指してはいけません。これは、どちらも a に基づいたポインタ値が代入されていないためです。b については、その宣言の const 修飾子から明らかですが、c については、f の本体の検査が必要です。
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); // undefined behavior c = d; f( 99, d+1, e); // undefined behavior f( 99, e, d+1); // OK }
c が b に関連付けられた配列を指すことは許可されていることに注意してください。また、これらの目的のために、特定のポインタに関連付けられた「配列」とは、配列オブジェクトのうち、そのポインタを通じて実際に参照される部分のみを意味することに注意してください。
上記の例では、b の const 性が関数本体で a に依存しなくなることを保証しているため、コンパイラは a と b がエイリアス化しないと推論できることに注意してください。同等に、プログラマは void f(int n, float * a, float const * restrict b) と書くことができ、その場合、コンパイラは b を通じて参照されるオブジェクトは変更されないと推論できるため、b と a の両方を使用して変更されたオブジェクトを参照することはできません。もしプログラマが void f(int n, float * restrict a, float * b) と書いた場合、コンパイラは関数の本体を調べることなく a と b の非エイリアス化を推論できないでしょう。
一般的に、関数のプロトタイプ内のすべての非エイリアス化ポインタに restrict を明示的に注釈付けするのが最善です。
[編集] ブロックスコープ
ブロックスコープの restrict 修飾されたポインタは、そのブロックに限定されたエイリアス化の主張を行います。これは、タイトなループなどの重要なブロックにのみ適用されるローカルな主張を可能にします。また、restrict 修飾されたポインタを取る関数をマクロに変換することも可能にします。
float x[100]; float *c; #define f3(N, A, B) \ do \ { 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]; \ } while(0)
[編集] 構造体メンバー
構造体のメンバーである restrict 修飾されたポインタによって行われるエイリアス化の主張のスコープは、構造体にアクセスするために使用される識別子のスコープです。
構造体がファイルスコープで宣言されている場合でも、構造体にアクセスするために使用される識別子がブロックスコープを持つ場合、構造体内のエイリアス化の主張もブロックスコープを持ちます。エイリアス化の主張は、オブジェクトがどのように作成されたかによって、ブロック実行または関数呼び出しの範囲でのみ有効になります。
struct t // Restricted pointers assert that { int n; // members point to disjoint storage. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u have block scope // r.p, r.q, s.p, s.q, u.p, u.q should all point to // disjoint storage during each execution of 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; }
実行結果の例
; generated code on 64bit Intel platform: foo: movl $5, (%rdi) ; store 5 in *a movl $6, (%rsi) ; store 6 in *b movl (%rdi), %eax ; read back from *a in case previous store modified it addl $6, %eax ; add 6 to the value read from *a ret rfoo: movl $11, %eax ; the result is 11, a compile-time constant movl $5, (%rdi) ; store 5 in *a movl $6, (%rsi) ; store 6 in *b ret
[編集] 参考文献
- C23標準 (ISO/IEC 9899:2024)
- 6.7.3.1 restrict の正式な定義 (p: TBD)
- C17標準 (ISO/IEC 9899:2018)
- 6.7.3.1 restrict の正式な定義 (p: 89-90)
- C11標準 (ISO/IEC 9899:2011)
- 6.7.3.1 restrict の正式な定義 (p: 123-125)
- C99標準 (ISO/IEC 9899:1999)
- 6.7.3.1 restrict の正式な定義 (p: 110-112)