英語版
このページの英語版を見る

Coralling Wild Pointers With ref return scope

ワイルドポインタとは、囲いから逃げ出したポインタ、つまり スコープから逃げ出したポインタのことである。スコープ内のポインタは有効であり、スコープ外の ポインタは無効である。スコープ外のポインタを使用して読み取りまたは書き込みを試みると、 未定義の動作が発生する。未定義の動作は、クラッシュ、破損、 マルウェア、その他の高額な問題につながる可能性がある。Dでは、refreturnscope というキーワードを使用して ポインタのエスケープを防止する。

スコープとは?

宣言のスコープは、その寿命と密接に関連している。 スレッドローカル変数の寿命は、そのスレッドが存在する間である。 グローバル変数の寿命は、プログラムの開始から終了までである。 ローカル変数の寿命は、その変数の初期化から波括弧で囲まれた部分の閉じ括弧までの間である。

例に関する注釈:@safe 注釈が想定されている

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
int x; // スレッドローカルの寿命
__gshared int y; // グローバルの寿命

void mars(int i /* iのライフタイムは関数呼び出しから関数戻りまでである */)
{
    { // 新しいスコープを開く
      int* q = &i;  // 寿命はqがiのアドレスに設定された後に始まる
      *q = 3;       // iを3に設定する
    }               // qのライフタイムはスコープの終了とともに終了する
    *q = 4;         // ここでqを使うことはできない
    int* p = &i;    // pがiのアドレスに設定された後、ライフタイムが始まる
    *p = 5;         // iを5にセットする
} // 関数が返却された時点でiとpの寿命は終了する

エスケープポインタとは?

ポインタの値がポインタのスコープ外で利用可能になった場合、ポインタがエスケープする。 エスケープするポインタの例:

ポインタの値がポインタのスコープ外で利用可能になった場合、ポインタがエスケープする。 エスケープするポインタの例:

to escapeエスケープ
int* escape()
{
    int i;
    int* p = &i; // ローカル変数へのポインタを作成する
    return p;    // もはや生きていないローカル変数へのポインタを返す
}

void crash()
{
    int* q = escape();
    *q = 5;  // 地獄の猟犬を解き放つ
}
ポインタのエスケープは、多くの場合、あまり明白ではないケースで発生する可能性がある。コンパイラは、 あらゆるケースを検知し、エラーとして報告する完璧なツールである。たとえ 特定のポインタエスケープが問題のないものであっても、関数

ポインタのエスケープは、多くの場合、あまり明白ではないケースで発生する可能性がある。コンパイラは、 あらゆるケースを検知し、エラーとして報告する完璧なツールである。たとえ 特定のポインタエスケープが問題のないものであっても、関数インターフェースが 関数の引数がエスケープできないことを明確に示している場合、関数の理解が深まる。

役割のscope

はストレージクラスである。ポインタ変数に適用されると、ポインタの 値は機械的に(すなわち、コンパイラによって強制的に)ポインタ変数のスコープを越えて存続することが防止される。 前の例を修正したものが次の例である。

scope ストレージクラスである。ポインタ変数に適用されると、ポインタの 値は機械的に(すなわち、コンパイラによって強制的に)ポインタ変数のスコープを越えて存続することが防止される。 先の例を修正して、 を追加する。scope

int* escape()
{
    int i;
    scope int* p = &i; // pはスコープ付きポインタである
    return p;    // エラー: スコープ付きポインタpはエスケープされている
}
この場合、コンパイラはさらに一歩進んだ対応をしてくれる。
int* escape()
{
    int i;
    int* p = &i; // pはスコープ付きポインタであると推測される
    return p;    // エラー: scoped pointer pはエスケープされている
}
すなわち、ポインタ変数がローカル変数のアドレス、またはスコープポインタの内容に設定されている場合、 そのポインタ変数は自動的にスコープポインタに設定される。 コンパイラは、推論が非常に得意である。

すなわち、ポインタ変数がローカル変数のアドレス、またはスコープポインタの内容に設定されている場合、 そのポインタ変数は自動的にスコープポインタに設定される。 コンパイラはスコープの推論に非常に優れているため、プログラマーは 多くの注釈を追加する必要がなくなる。

scope はストレージクラスであり、型コンストラクタではないため、 スコープポインタをスコープポインタに指定することはできない。驚くべきことに、それをサポートする必要はないことが判明した。

役割return scope

次のことを考えてみよう。

次のことを考えてみよう。

to isである
void f()
{
    int i;
    *process(&i) = 4;
}

int* process(scope int* p) { return p; }
これは完全に正当なコードであり、ポインタエスケープのバグはない。しかし、これはコンパイルできない。 パラメータxml-ph-0001@deeplにxml-ph-0000@deepl.internalを指定しないと。

これは完全に正当なコードであり、ポインタエスケープのバグはない。しかし、これはコンパイルできない。 パラメータpscope がないと、process(&i) への呼び出しは許可されない。 しかし、scopep があると、return p; は許可されない。

解決策は、return 注釈を追加することである。

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
int* process(return scope int* p) { return p; }

これにより、スコープポインタの値が関数から返されるようになる。

関数がvoid を返す場合、return scope は、スコープ値を 最初のパラメータを通じて返すことも可能にする。

xml-ph-0000@deepl.internalxml-ph-0001@deepl.internal
void mun(ref int* v, return scope int* p)
{
    v = p; // OK
}

ポインタについては以上である。ポインタのスコープはポインタ変数の値に関係していることを覚えておいてほしい。

役割ref

ref は、値への参照であり、値へのポインタを表現する凝った方法である。 ポインタとは区別される。

  1. xml-ph-0000@deepl.internal変数が使用されるたびに、アドレスの算術演算が許可されない
  2. ref 変数が使用されるたびに自動参照解除が実行される
  3. 参照は関数から抜け出せない
ref int fin(ref int i)
{
    return i; // エラー、ref変数iをrefで返却できない
}

その役割はreturn ref

しかし、xml-ph-0000@deepl.internal が適用された場合は許可される。

しかし、return が適用されれば許可される。

xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
ref int fin(return ref int i)
{
    return i; // OK
}

役割はref scope

ストレージクラスrefscope はそれぞれ独立しており、互いに影響を及ぼし合うことはない。ref は変数のアドレスを指し、scope は変数の内容を示す。

その役割はreturn ref scope

ここでいうreturnref に適用され、scope には適用されない。

コンパイラをだましてみよう:

xml-ph-0001@deepl.internalxml-ph-0001@deepl.internal
ref int* fin(return ref scope int* p) { return p; } // OK

int* tricky()
{
    int i;
    int* p = &i; // pはスコープであると推測される
    auto q = fin(p);  // qはiのアドレスを含むので、スコープも推論される
    return q;  // エラー: スコープ変数`q`は返却値ではないかもしれない
}
しまった!まただまされた!

しまった!また失敗した!

ここで考えられる対策は、xml-ph-0000@deepl.internalのコードをコンパイルする際に、 コードがどんなに複雑であっても、スコープ内の値をエスケープできないようにする

ここで考えられる対策は、@safe コードをコンパイルする際に、 コードがどんなに複雑であっても、スコープ内の値をエスケープできないようにすることだ。