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

Live関数

-preview=dip1021

所有権

メモリ・オブジェクトにポインタが1つしかない場合、そのポインタが所有者である。 である。所有者が1人であれば、オブジェクトのメモリを管理するのは簡単だ。 になる。また、複数のスレッド間でそのメモリー・オブジェクトへのアクセスを同期させるのも簡単だ。 複数のスレッド間でそのメモリー・オブジェクトへのアクセスを同期させるのも、つまらなくなる。 その単一のポインターをコントロールするスレッドによってのみアクセスできるからだ。

これは、ポインターによって相互接続されたメモリーオブジェクトのグラフに一般化できる、 ここで、ただ一つのポインターだけが、他の場所からそのグラフに接続する。その単一の ポインターは、そのグラフ内のすべてのメモリー・オブジェクトのオーナーとなる。

グラフのオーナーが不要になったら、ポインターが指すメモリ・オブジェクトのグラフも不要になり、安全に処分できる。 オブジェクトのグラフも不要になり、安全に処分できる。オーナー が使用されなくなり(つまりもう生きていない)、所有するメモリ・オブジェクトが処分されない場合、エラーと診断できる。 オブジェクトが廃棄されない場合、エラーが診断される。

したがって、以下のエラーは静的に検出できる:

int* allocate();    // メモリ・オブジェクトを確保する
void release(int*); // メモリ・オブジェクトの割り当てを解除する

@live void test()
{
    auto p = allocate();
}   // エラー: pは破棄されていない

@live void test()
{
    auto p = allocate();
    release(p);
    release(p); // エラー: pはすでに破棄された
}

@live void test()
{
    int* p = void;
    release(p); // エラー: pは定義された値を持っていない
}

@live void test()
{
    auto p = allocate();
    p = allocate(); // エラー: pは破棄されなかった
    release(p);
}

@live 」属性を持つ関数は、オーナー・ポインタの状態を追跡することで、この種のエラーを診断することができる。 オーナー・ポインタの状態を追跡する。

Borrowing

ポインタの所有状態を追跡することは、所有者から一時的にポインタの所有権を借りる機能を追加することで安全に拡張できる。 ポインタの所有権を所有者から一時的に借りることができる。 所有者は、借用者がまだポインタ値を使用している限り、ポインタを使用することはできない。 ポインタ値を使用している限り(つまり、ポインタは生きている)、所有者はポインタを使用できなくなる。借用者がもはや生きていない なると、オーナーはそのポインタの使用を再開できる。借手が生きているのは1人だけである。 である。

複数の借用ポインタが同時に存在できるのは、それらがすべて読み取り専用( または )のデータへのポインタである場合である。 const immutableポインターは同時に複数存在することができる。 オブジェクトを変更することはできない。

これを総称して「所有/借用システム」と呼ぶ。次のように言うことができる:

プログラム中のどの時点でも、各メモリ・オブジェクトに対して プログラム中のどの時点でも、各メモリ・オブジェクトに対して、それへの変更可能なライブ・ポインタが正確に1つあるか、それへのライブ・ポインタがすべて読み取り専用である。 ポインターはすべて読み取り専用である。

@live 属性

@live "関数"属性でアノテーションされた関数宣言は、所有/借用のルールに準拠しているかチェックされる。 チェックされる。チェックは、他のセマンティック処理が完了した後に実行される。 チェックはコード生成には影響しない。

ポインタがGCを使用してメモリを割り当てられたか、他のストレージ・アロケータを使用して割り当てられたかは、OBにとって重要ではない。 OBにとって、ポインタがGCや他のストレージ・アロケータを使ってメモリが割り当てられたかどうかは重要ではない。 同じように扱われる。

クラス参照はGCを使って割り当てられるか、 クラスとしてスタック上に割り当てられると仮定され、追跡されない。 スタック上にscope 、追跡されない。

@live "関数"が"関数"以外の関数を呼び出す場合、呼び出された関数は、 "関数"以外の関数を呼び出す前に、"関数"以外の関数を呼び出す前に、"関数"以外の関数を呼び出す。 が@live 以外の関数を呼び出す場合、呼び出された関数は と互換性のあるインターフェイスを提示することが期待される。 @live 互換のインターフェイスを提示することが期待される。 非@live 関数が@live 関数を呼び出す場合、渡される引数は の規約に従っていることが期待される。 @live の規約に従うことが期待される。

これは、null ポインタやおそらくは null ポインタの可能性もある。これは実行不可能である。 というのも、null 以外のポインタとして型をアノテートする方法が現在存在しないからである。

追跡ポインタ

追跡されるポインターは、@live 関数内で宣言されたものだけである。 this関数のパラメータまたはローカル変数として宣言されたものである。他の 他の関数からの変数は、@live であっても追跡されない。 他の関数との相互作用の解析は、その関数のシグネチャに依存する。 他の関数との相互作用の分析は、その内部ではなく、関数のシグネチャに依存するからである。 const であるパラメータは追跡されない。

ポインターの状態

各追跡ポインターは以下のいずれかの状態にある:

未定義
ポインタは無効な状態である。このようなポインタの再参照はエラーである。 エラーとなる。
所有者
オーナーはメモリ・オブジェクト・グラフへの唯一のポインターである。 Ownerポインタは通常、scope 属性を持たない。 もし、scope 属性を持つポインタが初期化された場合、そのポインタは追跡されたオブジェクトから派生したものでない。 を持つポインタが、追跡ポインタに由来しない式で初期化された場合、それはOwnerである。
Ownerポインタが別のOwnerポインタに代入されると、前者は未定義状態になる。 は未定義の状態になる。
void consume(int* o); // oは所有者である

@live int* f(int* p) // pは所有者である
{
    writeln(*p);
    // 所有権を`consume`に移す
    consume(p);
    // pは現在未定義である
    //writeln(*p); // エラー

    int* q = new int; // qはオーナーである
    writeln(*q);
    p = q; // 所有権を移す
    // qは現在未定義である
    //writeln(*q); // エラー
    writeln(*p);
    return p;
}
借用
借用ポインタとは、一時的に唯一のライブポインタとなるポインタのことである。 になることである。所有者ポインタまたは他の借用ポインタからの代入によってその状態になる。 オーナーポインタまたは別の借用ポインタからの割り当てによってその状態になり、最後に使用されるまでその状態に留まる。 に留まる。
借用ポインタはscope でなければならない。 へのポインタでなければならない。 ミュータブルscope ポインタ関数パラメータは借用ポインタである。
void consume(int* o); // oは所有者である
void borrow(scope int* b); // bは借用された

@live void g(scope int* p) // pは借用された
{
    //consume(p); // エラー、pは所有者ではない
    borrow(p);

    // pをqに貸す
    int* q = p; // qはスコープとして推論される
    // <-- ここでpを使うとqの寿命が終わる
    writeln(*q);
    // qのライフタイムはpが使用される前に終了する
    writeln(*p); // OK
}
読み取り専用
Readonlyポインタは、OwnerまたはBorrowedポインタから値を取得する。 Readonlyポインタが生きている間、そのOwnerから取得できるのはReadonlyポインタだけである。 ポインタしか取得できない。 Readonlyポインタはscope 、また、mutableポインタであってはならない。 へのポインタであってはならない。
@live void h(scope int* p)
{
    // 読み取り専用ポインタを2つ取得する
    const q = p;
    const r = q;
    // <-- ここでpを借りたり使ったりすると、qとrの寿命が終わる

    // qもrも生きている
    writeln(*q);
    writeln(*r);
    // pを使用すると、その読み取り専用ポインタの寿命がすべて終了する
    writeln(*p);
    //writeln(*q); // エラー
}

寿命

借用ポインタまたは読取専用ポインタの値の寿命は、それが所有者または他の借用ポインタから値を割り当てられたときに始まる。 所有者または別の借用ポインタから値が割り当てられたときに開始し、その値の最後の読み取りで終了する。 で終了する。

これは、Non-Lexical Lifetimesとしても知られている。

ポインターの状態遷移

ポインタの状態は、ポインタに対してこれらの操作が行われたときに変化する:

借り手はオーナーになれる

ポインタ以外から初期化された場合、借り手はオーナーとみなされる。 ポインタ以外から初期化された場合、借り手はオーナーとみなされる。

@live void uhoh()
{
    scope p = malloc();  // pはオーナーとみなされる
    scope const pc = malloc(); // pcはオーナーとみなされない
} // pcは終了時に検出されない

例外

この分析では、例外がスローされないことを前提としている。

@live void leaky()
{
    auto p = malloc();
    pitcher();  // 例外がスローされ、pがリークする
    free(p);
}

ひとつの解決策は、scope(exit) を使うことだ:

@live void waterTight()
{
    auto p = malloc();
    scope(exit) free(p);
    pitcher();
}

またはRAIIオブジェクトを使用するか、nothrow 関数のみを呼び出す。

遅延パラメータ

怠惰なパラメータは考慮されない。

メモリプールを混在させる

異なるメモリープールを混在させる:

void* xmalloc(size_t);
void xfree(void*);

void* ymalloc(size_t);
void yfree(void*);

auto p = xmalloc(20);
yfree(p);  // 代わりにxfree()を呼び出す必要がある

は検出されない。

これは、型別プールを使用することで軽減できる:

U* umalloc();
void ufree(U*);

V* vmalloc();
void vfree(V*);

auto p = umalloc();
vfree(p);  // 型の不一致

そしておそらく、@live 関数のvoid* への暗黙の変換を無効にする。

可変長引数関数

可変長引数関数(printf など)の引数は消費されるものとみなされる。

参考文献

  1. レースフリーのマルチスレッド:オーナーシップ