4

ブロックで、配列unsafeへのポインターを取得しようとしています。byteしかし、配列の宣言されたサイズに応じて、異なる結果が得られます。

unsafe {

    byte[] bytes;

    bytes = new byte[1];
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints e.g. 41797644
    }

    bytes = new byte[0];
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 0 ?!
    }
}

イミディエイト ウィンドウを開いて と入力&bytesすると、空の配列の場合も含めて、バイト配列の実際のアドレスが取得されます。

fixedアンマネージ ポインターが同じように機能しないのはなぜですか?

アップデート:

同じコードと、イミディエイト ウィンドウから取得したものを次に示します。

unsafe {
    byte[] bytes;
    bytes = new byte[1];
    fixed(void* pBytes = bytes)
    {
                       // bytes => 
                       // {byte[1]}
                       //    [0]: 0
                       //
                       // &bytes
                       // 0x0601c34c               //the address of the variable
                       //    bytes: 0x027dc804     //the address of the array
                       //
                       // pBytes
                       // 0x027dc80c               // notice pBytes == (&bytes + 8)
                       //     *pBytes: 0
    }

    bytes = new byte[0];
    fixed(void* pBytes = bytes)
    {
                       // bytes => 
                       // {byte[0]}
                       //
                       // &bytes
                       // 0x0601c34c               //same address of the variable, ofc
                       //    bytes: 0x02aa7ad4     //different address of (new) array 
                       //
                       // pBytes
                       // 0x00000000               // BOINK
                       //     *pBytes: Cannot dereference 'pBytes'. 
                       //              The pointer is not valid.
    }
}

配列オブジェクトのアドレス (&bytes) と配列ポインターの 8 バイトの違いは、オブジェクトのヘッダーによって説明されます。

メモリ内の配列表現は次のとおりです。

     type id  size     elem 0   elem1    ...
----|--------|--------|--------|--------|...
    ^ 4Bytes   4Bytes ^
    |                 `--< pBytes
    `--< &bytes

unsafe ポインターは実際には、実際のデータ (つまり、アンマネージ コンテキストにマーシャリングされるもの)の先頭を指しています。

空の配列の実際のアドレスをコードで取得する方法はありますか?

FWIW、実際には、配列のヘッダーにアクセスして、配列のランタイムタイプをその場で変更できるようにする必要があります。

4

2 に答える 2

12

固定アンマネージド ポインターが同じように機能しないのはなぜですか?

それは奇妙な質問です。なぜそうすべきだと思いますか?

コントラクトは次のとおりです。n > 0 の場合に n 個の要素を持つ配列を修正すると、n 個の要素を読み書きできるバッファへのポインタが取得されます。

ここで、n がゼロの場合、null は、ゼロ要素を読み書きできるバッファーへのポインターであるため、n がゼロの場合は、そのコントラクトが実際に満たされます。そのために C# 言語は必要ありません。仕様書によると

配列式が null の場合、または配列に要素がない場合、fixed ステートメントの動作は実装定義です。

したがって、実装は、プログラムで例外をスローするなど、完全にその権利の範囲内です。プログラムの意味は、実際には C# 言語仕様ではまったく定義されていません。

あなたはfixed信じられないほど危険で間違ったことをするために、適応外を使おうとしています。そうしないでください。配列で使用する必要fixedがあるのは、読み書きできる n 要素のバッファーへのポインターを取得するためだけです。

空の配列の実際のアドレスをコードで取得する方法はありますか?

はい。を使用して手動で固定しGCHandleます。

アドレスを取得するために管理対象オブジェクトを固定することは、ほとんどの場合危険であり、間違っています。

配列のヘッダーにアクセスして、配列のランタイム型をその場で変更できるようにする必要があります。

それは常に危険で間違っています。いかなる状況でもそれをしないでください。

于 2013-06-13T16:03:38.870 に答える
1

アドレスを取得する方法は、GCHandle.

.net オブジェクトのアドレス(ポインタ)を取得するには、GCHandle を参照してください。

GCHandle handle;
IntPtr ptr;
byte[] bytes;

bytes = new byte[1];
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

ptr = handle.AddrOfPinnedObject();
ptr.ToInt32().Dump(); // Prints 239580124

handle.Free();

unsafe {
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 239580124
    }
}

bytes = new byte[0];
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

ptr = handle.AddrOfPinnedObject();
ptr.ToInt32().Dump(); // Prints 239609660

handle.Free();

unsafe {
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 0
    }
}

このように機能する理由については、Eric Lippert の回答を参照してください。

于 2013-06-13T15:55:11.950 に答える