C99 では、プログラマがポインタがスコープ内の特定のオブジェクトへの唯一のポインタであることを指定し、その結果コンパイラに「ヒント」を与える方法として、restrict キーワードを追加しました。 ” そのポインタを介してオブジェクトにアクセスするときに追加の最適化を実行する可能性があります。
制限が解決しようとしていた問題を説明するために、次のような関数を考えてみましょう。
void update_ptrs( int *p, int *q, int const *v ) { *p += *v; *q += *v; }
コンパイラは次のような x86-64 コードを生成します。
mov eax, [rdx] ; tmp = *v // 1 add [rdi], eax ; *p += tmp mov eax, [rdx] ; tmp = *v // 3 add [rsi], eax ; *q += tmp
行 1 と重複しているように見えるのに、なぜ行 3 が生成されるのか疑問に思われるかもしれません。問題は、コンパイラーは次のようなことを行っていないことを認識できないことです。
int x = 1, v = 2; update_ptrs( &v, &x, &v ); // x = 5, v = 4
update_ptrs() では、p と v は 同じ int に 別名 を付けるため、コンパイラーは安全策を講じて、*v の値が読み取り間で変更される可能性があると想定する必要があります。したがって、追加の mov 命令です。
一般に、C のポインターは最適化を混乱させます。これは、コンパイラーが 2 つのポインターが互いにエイリアスであるかどうかを認識できないためです。 パフォーマンスが重要なコードでは、コンパイラがメモリ読み取りを安全に実行できれば、メモリ読み取りを省略することが可能性があります。場合。
前述の問題を解決するために、指定されたポインタがそのポインタのスコープ内のオブジェクトへの唯一のポインタであること、つまり、同じスコープのエイリアス内に他のポインタがないことを指定できるように、Cにrestrictが追加されました。それ。
restrict を使用するには、宣言内の * とポインターの名前の間にを挿入します。 制限を使用するように書き換えられた update_ptrs() は次のようになります:
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p += *v; *q += *v; }
(右から左に読みます。たとえば、v は定数 int への制限付きポインタです。または、cdecl を使用します。)
制限を追加することで、コンパイラは次のようなコードを生成できるようになります。
mov eax, [rdx] ; tmp = *v add [rdi], eax ; *p += tmp add [rsi], eax ; *q += tmp
これで、コンパイラは追加の mov 命令の前の行 3 を省略できました。
おそらく、restrict が使用される最もよく知られた例は、標準ライブラリ関数 memcpy() です。 これは、ソース アドレスと宛先アドレスが重複しない場合、メモリのチャンクをコピーする最速の方法です。アドレスが 重複している場合に使用するために、少し遅い memmove() 関数が存在します。
落とし穴一部の場合には、コンパイラーは警告を発しますが、すべての場合ではないため、誤用を検出するためにコンパイラーに依存しないでください。 制限は特定のスコープに対するものであることに注意してください。 1 つの制限されたポインタを 同じスコープ
内の別の制限されたポインタに割り当てると、未定義の動作が発生します:
ただし、制限付きポインターを制限なしポインターに割り当てることは問題なくできます。
void f( int *restrict d, int *restrict s ) { int *restrict p = s; // undefined behavior
p に制限がない場合でも、コンパイラーは同じ最適化を実行できます。
void f( int *restrict d, int *restrict s ) { int *p = s; // OK内部スコープの制限されたポインターを外部スコープの別のポインターに割り当てることもできます (ただし、その逆はできません)。
制限を使用する場合 (および使用しない場合)
void f( int *restrict d, int *restrict s ) { { // inner scope int *restrict p = s; // OK // ... s = p; // undefined behavior } }まず、必ずコードのプロファイリングを行って (おそらく生成されたアセンブリ コードも確認して)、潜在的な落とし穴の危険を冒して正当化できるほど、restrict を使用することで実際に
非常に困難です。 2 番目に、制限の使用が、制限されたポインターを介してアクセスされるメモリが あなた
によって割り当てられた関数の実装に限定されている場合は、より安全です。 たとえば、次のようになります。
配列の前半と後半は重複していないため、コードは安全に操作できます (half_1st[n/2] 以降にアクセスしないと仮定します)。
void safer( unsigned n ) { n += n % 2 != 0; // make even by rounding up int *const array = malloc( n * sizeof(unsigned) ); unsigned *restrict half_1st = array; unsigned *restrict half_2nd = array + n/2; // ... free( array ); }第三に、関数のパラメーターで制限が使用されている場合、安全性が
低く
なる可能性があります。 たとえば、safer() と、呼び出し元 がポインタを制御する update_ptrs_v2() を対比します。 あなたもご存知のとおり、呼び出し元は間違ってい、エイリアスというポインタを渡しました。 その他
で修飾できます。
構造体メンバーには制限を使用できます。例:
restrict int x; // error: can't restrict object int restrict *p; // error: pointer to restrict object int (*restrict f)(); // error: pointer-to-function
は、データがそのデータへの唯一のポインタであり、左右が同じノードを指すことは決してないと言っています。 ただし、構造体のメンバーに制限を使用することは非常に一般的ではありません。
struct node { void *restrict data; struct node *restrict left; struct node *restrict right; };最後に、C++ には制限が
ありません
。 なぜだめですか? 長い答えがありますが、TL;DR バージョンは次のとおりです:ただし、多くのコンパイラには拡張子として __restrict__ があります。
限られたケースでは、restrict を使用するとパフォーマンスが向上する可能性がありますが、いくつかの重大な落とし穴があります。 制限の使用を検討している場合は、まずコードをプロファイリングしてください。
賢く使用してください。
以上がC のあいまいな「restrict」キーワードの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。