読者です 読者をやめる 読者になる 読者になる

日本語が不自由な緑の箱の日記

主にD言語とロボコンを

void[]

D言語にはvoidという関数やメソッドの返り値の型に指定することが出来る値を持たない型があります。void*という何のポインタかは分からないがとりあえずポインタであることを示す型があります。そして、void[]と言う狐につまさたような型があります。 dlang.orgには次のように記述されています。

原文

There is a special type of array which acts as a wildcard that can hold arrays of any kind, declared as void[]. Void arrays are used for low-level operations where some kind of array data is being handled, but the exact type of the array elements are unimportant. The .length of a void array is the length of the data in bytes, rather than the number of elements in its original type. Array indices in indexing and slicing operations are interpreted as byte indices.

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[] implicit converts to void[].
    assert(data1.length == 3);
    assert(arr.length == 12);      // length is implicitly converted to bytes.

    //data1 = arr;                 // Illegal: void[] does not implicitly
                                   // convert to int[].
    int[] data3 = cast(int[]) arr; // OK, can convert with explicit cast.
    data2 = cast(long[]) arr;      // Runtime error: long.sizeof == 8, which
                                   // does not divide arr.length, which is 12
                                   // bytes.
}

Void arrays can also be static if their length is known at compile-time. The length is specified in bytes:

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, lengths match
    void[2] b = y; // Error: int[2] is 8 bytes long, doesn't fit in 2 bytes.
}

While it may seem that void arrays are just fancy syntax for ubyte, there is a subtle distinction. The garbage collector generally will not scan ubyte arrays for pointers, ubyte being presumed to contain only pure byte data, not pointers. However, it will scan void arrays for pointers, since such an array may have been implicitly converted from an array of pointers or an array of elements that contain pointers. Allocating an array that contains pointers as ubyte[] may run the risk of the GC collecting live memory if these pointers are the only remaining references to their targets.

意訳

これは任意の配列を取るための特殊な型で、void[]と宣言されます。void[]は配列の要素の値が重要でない場合に低レベルな配列の操作をする際に使われます。.lengthプロパティは元の型の配列での.lengthの値ではなく、その配列のbyteでの大きさを返します。void[]でのインデックスアクセスやスライスはbyteの配列に対するそれらの操作として解釈されます。

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[]はvoid[]に暗黙の返還が出来る.
    assert(data1.length == 3);
    assert(arr.length == 12);      // .lengthは暗黙にbyteのものとして扱われる.

    //data1 = arr;                 // 不正な操作 : void[]はint[]に暗黙に変換できない.
                                   // int[]に変換。
    int[] data3 = cast(int[]) arr; // OK, 明示的なint[]への変換は出来る.
    data2 = cast(long[]) arr;      // Runtime error: longのサイズは8バイトであるため、
                                   //12バイトのvoid[]をうまく分割出来ないため 
                                   //ランタイムエラーになる. 
}

void[]コンパイル時にその長さが判明しているならば、void[12]のように静的配列にすることも出来ます。

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, 同じ長さ.
    void[2] b = y; // Error: int[2]の大きさは8バイトなので2バイトのvoid[2]には変換できない.
}

void[]ubyte[]はちょっとした構文上の違いのようですが、微妙な違いがあります。ubyte[]は純粋なバイト列のデータとみなされるために、GCのスキャンの対象となりません。しかし、void[]はポインタを含む任意の配列から変換された可能性がある為、GCののスキャンの対象となります。つまり、ubyte[]だとそこに何か生きているデータへのポインタが含まれていた場合に、もしそのポインタ以外の参照が無ければGCに回収されてしまうリスクが出来てしまうということです。

問題点

このvoid[]任意の型TにおけるT[]から暗黙に変換できてしまうと言うのが中々の曲者で、場合によっては予想外の動作を引き起こします。

例えば、再帰を使って各要素をそれぞれ2乗するコードです。

import std.stdio;

void main() {
    [1,2,3].square.writeln;
}

auto square(int[] ary) {
    if (ary.length == 0) return [];
    return [ary[0] ^^ 2] ~ square(ary[1..$]);
}

このコードを実行すると僕の環境では[1, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0]と言う結果が得られます。 この関数squareでは2つのreturn文が存在します。まず、[]はvoid[]なので、片方の型はvoid[]です。そしてreturn [ary[0] ^^ 2] ~ square(ary[1..$]);の型はint[] ~ typeof(square([334/+適当+/])の結果となります。int[] -> void[]への暗黙の変換は出来ますが、void[] -> int[]への暗黙の変換は出来ないのでsquareが返す値の型はvoid[]と推論されてしまいintの配列ではなく結果のバイト配列を返します。x86はリトルエンディアンの為、1,2,3はそれぞれ[1,0,0,0],[4,0,0,0],[9,0,0,0]で表されるバイト列に変換されます。

この場合は[]int[]にcastするか、返り値の型をint[]と明示してやれば[1,4,9]と期待通りの結果が得られます。