Cプログラミング言語では、はC99標準[1]で導入されたキーワードrestrictであり、ポインタ宣言で使用できます。この型修飾子を追加することで、プログラマは、ポインタの有効期間中、ポインタが指すオブジェクトにアクセスするために他のポインタが使用されないことをコンパイラに通知します。これにより、コンパイラは、他の方法では不可能だった最適化(ベクトル化など)を行うことができます。
restrictポインタエイリアシングの影響を制限し、最適化を支援します。意図の宣言に従わず、オブジェクトが独立したポインタによってアクセスされた場合、未定義の動作が発生します。
最適化
コンパイラがメモリ ブロックへのポインタが 1 つしかないことを認識している場合、より最適化されたコードを生成できます。例:
void updatePtrs ( size_t * ptrA 、size_t * ptrB 、size_t * val ) { * ptrA += * val ; * ptrB += * val ; }
上記のコードでは、ポインタptrA、ptrB、 が同じメモリ位置を参照しているval 可能性があるため、コンパイラは最適でないコードを生成する可能性があります。
; 仮想 RISC マシン。
ldr r12 , [ val ] ; val のメモリを r12 にロードします。ldr r3 , [ ptrA ] ; ptrA のメモリを r3 にロードします。add r3 , r3 , r12 ; 加算を実行します: r3 = r3 + r12。str r3 , [ ptrA ] ; r3 をメモリ位置 ptrA に格納し、値を更新します。ldr r3 , [ ptrB ] ; 'load' は、先行する 'store' が完了するまで待機する必要がある場合があります。ldr r12 , [ val ] ; 一貫性を確保するために、2 回目のロードが必要です。add r3 , r3 , r12 str r3 , [ ptrB ]
restrictしかし、キーワードが使用され、上記の関数が次のように宣言されている
場合、
void updatePtrs ( size_t * restrict ptrA 、size_t * restrict ptrB 、size_t * restrict val );
この場合、コンパイラは、、、が異なる位置を指していると想定でき、1 つのポインタによって参照されるメモリ位置を更新しても、他のポインタによって参照されるメモリ位置は影響を受けません。ポインタが同一の位置を指していないことを確認する責任は、コンパイラではなくプログラマにあります。コンパイラは、たとえば、コードを再配置して、最初にすべてのメモリ位置をロードし、次に操作を実行してから結果をメモリに戻すことができます。
ptrAptrBval
ldr r12 , [ val ] ; val が一度だけロードされることに注意してください。ldr r3 , [ ptrA ] ; また、すべての「ロード」は最初にあります... ldr r4 , [ ptrB ] add r3 , r3 , r12 add r4 , r4 , r12 str r3 , [ ptrA ] ; ...すべての「ストア」は最後にあります。str r4 , [ ptrB ]
上記のアセンブリ コードは、一度しかロードされないため短くなりますval。また、コンパイラはコードをより自由に並べ替えることができるため、コンパイラはより高速に実行されるコードを生成できます。上記の例の 2 番目のバージョンでは、storeすべての操作が操作の後に実行されるloadため、プロセッサはコードの途中でブロックしてstore操作が完了するまで待機する必要がありません。
実際に生成されるコードの動作は異なる場合があることに注意してください。上記のミニ例による利点は小さい傾向があり、実際のケースでは、大量のメモリ アクセスを実行する大規模なループが、restrict によって実際に役立つ傾向があります。
前述のように、不正なコードの動作は未定義であり、コンパイラは、コードが意図の宣言に従っている場合にのみ、生成されたコードが適切に動作することを保証します。
C++コンパイラによるサポート
C++には の標準サポートはありませんrestrictが、多くのコンパイラには、GCCの やClangの__restrict__、Visual C++のなど、C++ と C の両方で通常動作する同等の機能があります__declspec(restrict)。さらに、__restrictはこれら 3 つのコンパイラでサポートされています。これらの代替キーワードの正確な解釈は、コンパイラによって異なります。
- GCCやClangなどのUnixスタイルのコンパイラでは、
__restrictおよびは__restrict__Cの対応するものとまったく同じ意味を持ちます。拡張機能には、参照型やに適用できるようにする機能が含まれますthis。[2] - Visual C++ では、複数の no-alias 修飾子が提供されています。
__declspec(restrict)関数宣言に適用され、返されるポインタがエイリアス化されていないことを示します。__restrictは と同じ場所で使用されますrestrictが、 のように no-alias ヒントは伝播しません。これは共用体型restrictにも拡張されます。
コンパイラの警告
不正なコードを防ぐために、一部のコンパイラやその他のツールでは、 とマークされたパラメータを持つ関数に重複した引数が渡されたかどうかを検出しようとしますrestrict。[3] CERT Cコーディング標準では、と でマークされたライブラリ関数 (EXP43-C)の誤用はrestrictソフトウェアバグの原因となる可能性があると考えられていますが、2019年11月現在、これによって脆弱性が発生したという報告はありません。[4]
参考文献
- ^ Drepper, Ulrich (2007 年 10 月 23 日)。「メモリ パート 5: プログラマーができること」。すべてのプログラマーがメモリについて知っておくべきこと。lwn.net 。... C および C++ 言語のデフォルトのエイリアス ルールは、コンパイラーがこれらの決定を行うのに
役立ちません (restrict を使用しない限り、すべてのポインター アクセスはエイリアスの原因になる可能性があります)。これが、Fortran が数値プログラミングに今でも好まれる言語である理由です。Fortran を使用すると、高速なコードを簡単に記述できます。(理論上は、1999 年の改訂で C 言語に導入された restrict キーワードによって、この問題が解決されるはずです。ただし、コンパイラーはまだ追いついていません。主な理由は、コンパイラーを誤導し、誤ったオブジェクト コードを生成する原因となる誤ったコードが多すぎることです。)
- ^ 「制限付きポインタ」。GNUコンパイラ コレクション (GCC) の使用。
- ^ 「警告オプション: -Wrestrict」。GCC 。 2019年11月19日閲覧。
- ^ 「EXP43-C. 制限修飾ポインターを使用する場合は未定義の動作を避ける」SEI CERT C コーディング標準。2019年11 月 19 日閲覧。
- 「ISO/IEC 9899:TC2 委員会草案」(PDF) . ISO . 2005 年 5 月 6 日 . pp. 108–112 . 2008 年 12 月 22 日閲覧。
外部リンク
- 制限キーワードの謎を解く: 説明と使用例
- Walls, Douglas. 「C で restrict 修飾子を使用する方法」。Oracle™ 。2012 年 11 月 21 日閲覧。
- C言語における制限付きポインタ: 定義の背後にある本来の根拠
