yohhoyの日記

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

続 restrictキーワード

C99で導入された restrict キーワードに関するメモ。restrictキーワード の続き。

読込アクセスとalias

restrictポインタが指すオブジェクトに対して変更(書き込み)を行わなければ、restrictポインタ同士が同一オブジェクトを指していても良い。下記コードでは関数h内のrestrictポインタqとrは同一オブジェクトbを指しているが、関数hではqやrが指すオブジェクトの更新を行っていない。(6.7.3.1/p10)

void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
  int i;
  for (i = 0; i < n; i++)
    p[i] = q[i] + r[i];
}

int a[100], b[100];
h(100, a, b, b);  // well-defined

オブジェクトのalias

restrictキーワードが意味する「aliasが存在しない」とは、restrictポインタが指す先の「オブジェクト」に対する表明となる。下記コードでは x と *p は同一オブジェクトを指しており、また処理(1), (2)は同オブジェクトの変更を行っている。このケースはrestrictの要件に違反しているため未定義動作となる。(6.7.3.1/p4)

int x;
{
  int * restrict p = &x;
  ++(*p);  // (1) restrictポインタpを介したオブジェクト更新
  ++x;     // (2) 左辺値xを介したオブジェクト更新
  // undefined behavior!
}

仮に、処理(1)または(2)のいずれか一方のみを行う、もしくはオブジェクトに対する変更を行われなければwell-defiend。

no-aliasの表明範囲

restrictポインタが指すオブジェクトのaliasが存在しないと表明する範囲は、restrictポインタ型変数を宣言した場所に応じて3種類に分類される。(6.7.3.1/p2)

  • [A] restrictポインタ型変数が関数定義中のパラメータリストで宣言されるとき、該当関数の実行中にaliasが存在しないとみなす。
  • [B] (非externな)restrictポインタ型変数がブロック内で宣言されるとき、該当ブロックの実行中にaliasが存在しないとみなす。
  • [C] 上記いずれにも該当しないとき、プログラム全体の実行中にaliasが存在しないとみなす。
int * restrict p3_1;             // [C] プログラム全体

void func(int * restrict p1)     // [A] 関数本体
{
  extern int * restrict p3_2;    // [C] プログラム全体
  //...
  {
    int * restrict p2_1;         // [B] ブロック
    static int * restrict p2_2;  // [B] ブロック
    //...
  }
}

メンバ変数への使用

構造体メンバ変数にrestrictポインタを持つ場合、no-alias表明範囲は構造体変数を宣言した場所に依存する。下記コードにおいてs1.a1とs1.a2はプログラム全体が表明範囲となり、s2.a1とs2.a2は関数f3の本体のみが表明範囲となる。(Rationale Rev5.10 6.7.3.1)

struct t {
  int n;
  float * restrict a1, * restrict a2;
};
struct t s1;
void f3(struct t s2) { /* ... */ }

標準Cライブラリでの使用

C99では古くからあるC標準ライブラリ関数に対して、一部のポインタ型引数にrestrict修飾を追加しており、おおまかに下記の3パターンに分類できる。(Rationale Rev5.10 7.1)

  • [a] 効率的な実装のために範囲オーバーラップを許容しない関数。代表例:memcpyのコピー元/コピー先。
  • [b] 書式文字列と可変引数リストを指定する関数。代表例:printfの書式文字列。
  • [c] char型ポインタと他ポインタを指定する関数。代表例:fgetsの文字列バッファとFILEポインタ。
// [a]
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
// [b]
int printf(const char * restrict format, ...);
// [c]
char *fgets(char * restrict s, int n, FILE * restrict stream);

パターン[c]は、コンパイラは「char型ポインタは任意オブジェクトのaliasとなりうる」というaliasing ruleに基づき*1、char型と他ポインタがaliasになる可能性を考慮しなければならない*2。この仮定が不要であるとコンパイラに表明し、ライブラリ関数の効率的な実装が行えるようrestrict修飾が追加されている。

関連URL

*1:6.5/p7

*2:セマンティクスとしてFILEポインタと文字列がaliasになるような利用はありえないが、仕様に厳密に従う限りaliasの可能性を仮定したコード生成を行わなければならない。