C# Unsafe Code - かきかけ

C#のUnsafe Codeをやってみよう。C# Specの18章。

あらかじめ、プロジェクトの設定からunsafe codeを使えるように変更しておきましょう。

(この記事は全体的にC#の仕様書のコピーです)

要するにUnsafeってナニができるんだ?

C#内部でC言語風のポインタがいじれます。それとcallocまがいのことができます。
C言語のような罠多めのポインタは使えませんが、使い方を間違えれば例外無しでアプリが落ちるくらいのことにはなります。(セグメンテーションフォルトなど)
一部条件下で使用すると動作が実装依存だったり、未定義だったりすることがあります。
なるべく使わないに越したことはありません。

Unsafeをつけられるところ

unsafeキーワードつけたものがunsafe操作可能になります。要するにポインタをごにょごにょできるようになる邪悪の印です。

  • class-modifier
  • struct-modifier
  • interface-modifier
  • delegate-modifier
  • field-modifier
  • method-modifier
  • property-modifier
  • event-modifier
  • indexer-modifier
  • operator-modifier
  • constructor-modifier
  • destructor-declaration
  • static-constructor-modifiers
  • block

ぜんぶですね。

ポインタ型

基本的にC言語と同じ:

            int i = 3;
            int* j = &i;
            Console.WriteLine(*j); // prints 3
            *j = 5;
            Console.WriteLine(*j); // prints 5

これをやると死す:

            j = (int*) 0;
            Console.WriteLine(*j); // no exception is thrown, but this application will be aborted.

アドレスを表示:

            Console.WriteLine((uint) j); // prints address
*void型も使えるよ。
        struct SomeStruct
        {
            public double Member;
            // cound not contain the safe-types.
        }

        static unsafe void Main(string[] args)
        {
            void* anyTypeAssignable;
            int intVal = 4;
            anyTypeAssignable = &intVal;
            SomeStruct structVal = new SomeStruct() { Member = -120.124114 };
            anyTypeAssignable = &structVal; // 直接 new SomeStruct()~~を代入してもよい

            Console.WriteLine(*((int*)anyTypeAssignable)); // doubleな型の一部をプリント
        }

ポインタに指定できる型

An unmanaged-type is any type that isn’t a reference-type and doesn't contain reference-type fields at any level of nesting. ~~

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool. (要するにプリミティブ型.)
  • Any enum-type.
  • Any pointer-type.
  • Any user-defined struct-type that contains fields of unmanaged-types only. (object, stringなどは含めません。また、メンバのメンバに関しても再帰的に含めません。)
object* fuga; // 不正
&new object(); // 不正
int[]* fuga; // 不正
int*[] fuga = {null, null}; // 合法

ポインタ演算子は変数名にかかりません

// a も b もint*
int* a, b;
int *a, b;

// 不正
int a, *b;

->(arrow) 演算子

C言語と同じ。void*型以外に対して->を作用させて、直接メンバを参照することが可能。

(&new SomeStruct())->member = 3;
fixed ステートメント

ステートメント中の変数のアドレスを固定させるステートメント

fixed( <pointer-type> varref1= &var1, var2 = expr ... )
{
   // varref1, var2 などfixedで定義した変数に再代入不可
}

そもそもfixedさせる必要があるの?というと…

The address-of operator (§18.5.4) and the fixed statement (§18.6) divide variables into two categories: Fixed variables and moveable variables.

fixed variables
Fixed variables reside in storage locations that are unaffected by operation of the garbage collector. (Examples of fixed variables include local variables, value parameters, and variables created by dereferencing pointers.)
moveable variables
On the other hand, moveable variables reside in storage locations that are subject to relocation or disposal by the garbage collector. (Examples of moveable variables include fields in objects and elements of arrays.)

配列の要素とかはfixedなモノじゃないってことですね。

The & operator (§18.5.4) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using a fixed statement (§18.6), and that address remains valid only for the duration of that fixed statement.

そもそもfixedじゃないアドレスは普通に&で取得できないようです。やってみましょう。

            int[] hoge = {1,2,3,4,5 };
            int* refToIntArrayElem = &hoge[0]; // 不法. fixed ステートメントの初期化子内の fixed でない式のアドレスのみを取得できます。

            fixed (int* trueRef = &hoge[0]) { } // 合法.

ですが、fixedなアドレスも普通に&で取得できます。fixedステートメントの濫用はヒープのフラグメンテーションを発生させるので薦められないとかなんとか。

外部APIとのやりとりに使えるそうな。JNIでも同じようなのがあったと思う。

ポインタと型互換

string型はchar*に変換できます。(fixedステートメント内のみ)

            fixed(char* stringconversion = "hogehoge"){
                for (char* cp = stringconversion; *cp != '\0'; cp++ )
                {
                    Console.WriteLine(*cp);
                }
            }

Cと同じくNull文字がterminationです。外部APIに文字をそのまま渡せる便利さがあります。

普通の配列型も先頭の要素へのポインタに自動変換できます。

            fixed (int* it = new int[1, 2, 3, 4]){}

stackalloc

スマートポインタみたいなもんです。スタックを抜けるまで絶対に回収できないcallocを提供します。

{
                int* hoge = stackalloc int[1000]; // すべて0で初期化される.
                for (int i = 0; i < 1000; i++)
                {
                    // C と違ってi[hoge]は違法. Cの仕様同様*(i + hoge)と等しいと説明されるがそうではない模様.
                    Console.Write(hoge[i]);
                    Console.Write(*(hoge + i)); // もちろん合法
                    Console.WriteLine(*(i + hoge)); // こちらも合法
                }
            }
            // stackallocした領域は無効

確保位置のスタックを越えれば蒸発します。仕様書のサンプルではunsafeメソッド内で確保したstackallocなchar*をmanaged type(string)に置き換えなおして外部に返却する例を載せています。

確保した領域を外部APIに渡す場合、スタックを抜けてリリースされないようにするのはC#ユーザの責任で行われなくてはいけません。

もしスタックを越えるアロケーション(=ヒープ領域)が欲しい場合には自前でライブラリを用意しなくてはいけないそうです。

fixed size buffers

C#3.0からの機能かな。

Fixed size buffers are used to declare “C style” in-line arrays as members of structs, and are primarily useful for interfacing with unmanaged APIs.

要するに外部APIとやりとりするときに便利な機能ってことです。

A fixed size buffer is a member that represents storage for a fixed length buffer of variables of a given type.

memberとか書かれていますがstructのメンバのことです。

struct Hoge
{
	public unsafe fixed int Kyle[100], Kyle2[1000];
}

かきかけ

** 実装依存、あるいはあなたの実装に依存

かきかけ

** notes

- The type specified before the * in a pointer type is called <em>the referent type</em> of the pointer type. It represents the type of the variable to which a value of the pointer type points.

*1224839075*[食]2008/07に賞味期限が来ている納豆を食べようと思う
納豆パスタにしてたべたよ。おいしかったよ。

ただやっぱり臭い。そして発酵しまくったもの独特の味わいがある(ピータンみたいな)。チロシンの結晶がジャリジャリいっている。