第21章 ポインタを使用できる「安全でないコード」:連載 改訂版 C#入門(2/4 ページ)
連載最終回となる今回は、C#におけるポインタの利用について解説する。ポインタのサポートにより、C#では低レベルな処理も効率よく行うことができる。
21-3 固定変数と移動可能変数
&演算子によって変数への参照を得ることができることは、List 21-1で示したとおりである。しかし、どんな変数への参照でも取得できるわけではない。List 21-3は、クラスのインスタンスなど幾つかの種類の変数に&演算子を適用してみた例である。
1: using System;
2:
3: namespace Sample003
4: {
5: class SampleClass
6: {
7: public int x=123;
8: }
9: struct SampleStruct
10: {
11: public int x;
12: }
13: class Class1
14: {
15: unsafe static void test()
16: {
17: int i=123;
18: int * p1 = &i;
19: Console.WriteLine(*p1);
20: SampleClass sampleClass = new SampleClass();
21: // SampleClass * p2 = &sampleClass;
22: // マネージ型 ('Sample003.SampleClass') の変数のアドレスまたはサイズを取得できません。
23: // マネージ型 ('Sample003.SampleClass') の変数のアドレスまたはサイズを取得できません。
24: // Console.WriteLine(*p2);
25: SampleStruct sampleStruct;
26: sampleStruct.x = 123;
27: SampleStruct * p3 = &sampleStruct;
28: Console.WriteLine(p3->x);
29: object sampleStruct2 = (object)sampleStruct;
30: // SampleStruct * p4 = &(SampleStruct)sampleStruct2;
31: // fixed ステートメントの初期化子内の fixed でないステートメントのアドレスのみを取得できます。
32: // 式のアドレスを取得できません。
33: // Console.WriteLine(p4->x);
34: }
35: [STAThread]
36: static void Main(string[] args)
37: {
38: test();
39: }
40: }
41: }
これを実行するとFig.21-4のようになる。
コンパイル・エラーになる組み合わせはコメントアウトしてある。まず、17〜19行目のローカルな整数型変数への参照は問題なく処理されていることが分かる。しかし、20〜24行目でクラスのインスタンスの参照を得ることは、コンパイル・エラーになってしまってできない。25〜28行目はローカルな構造体への参照を得ているが、これは正常にコンパイル実行できる。29〜33行目は構造体をボックス化してインスタンス化しているが、この場合もコンパイラがエラーにする。
さて、このようにコンパイルできるものとできないものに分かれた理由は、データを格納するメモリには2つの種類が存在することによる。1つは固定変数、もう1つは移動可能変数である。固定変数は、.NET Frameworkのメモリ管理機構のメモリ整理によって位置が移動しないもの、移動可能変数は移動してしまう可能性があるものだ。参照を取得できるのは固定変数に限られる。移動可能変数では、参照先が移動してどこかに行ってしまう可能性があり、参照できても正常に処理できないのである。
さて、ローカル変数はスタック上に確保されるが、スタックは基本的に移動しないものであるため、これに対する参照は問題なく得られる。これが17〜19行目の処理がうまく動く理由だ。次に、20〜24行目もローカル変数のように見えるが、実際のインスタンス本体はスタック外に移動可能変数として確保されるので、これは参照できない。25〜28行目は構造体の例だが、クラスと違って、ローカル変数として確保すれば中身のすべてがスタック上に存在するため、参照を取得できる。しかし、29〜33行目のように、構造体をボックス化するとスタック外に移動可能変数として確保されてしまうので、参照は取得できない。
21-4 移動可能変数メモリを参照するfixedステートメント
移動可能変数メモリへの参照は、何もしなければ得ることができない。しかし、得る方法がないわけではない。fixedステートメントは、移動可能変数メモリを一時的に固定させ、それに対する参照を得られるようにする機能を持つ。List 21-4はそれを用いた例である。
1: using System;
2:
3: namespace Sample004
4: {
5: class SampleClass
6: {
7: public int x=123;
8: }
9: struct SampleStruct
10: {
11: public int x;
12: }
13: class Class1
14: {
15: unsafe static void test()
16: {
17: int i=123;
18: int * p1 = &i;
19: Console.WriteLine(*p1);
20: SampleClass sampleClass = new SampleClass();
21: // fixed( SampleClass * p2 = &sampleClass )
22: // マネージ型 ('Sample004.SampleClass') の変数のアドレスまたはサイズを取得できません。
23: // マネージ型 ('Sample004.SampleClass') の変数のアドレスまたはサイズを取得できません。
24: // 既に fixed が使用されている式のアドレスを取得するときに、fixed ステートメントを使用する必要はありません。
25: //{
26: // Console.WriteLine(*p2);
27: //}
28: fixed( int * p3 = &sampleClass.x )
29: {
30: Console.WriteLine(*p3);
31: }
32: SampleStruct sampleStruct;
33: sampleStruct.x = 123;
34: SampleStruct * p4 = &sampleStruct;
35: Console.WriteLine(p4->x);
36: object sampleStruct2 = (object)sampleStruct;
37: // fixed( int * p5 = &((SampleStruct)sampleStruct2).x )
38: // 式のアドレスを取得できません。
39: //{
40: // Console.WriteLine(*p5);
41: //}
42: }
43: [STAThread]
44: static void Main(string[] args)
45: {
46: test();
47: }
48: }
49: }
これを実行するとFig.21-5のようになる。
List 21-4の内容はList 21-3の例とほぼ同じだが、fixedステートメントを使用するようになっている。
まず、List 21-3で駄目だったインスタンスへの参照だが、21〜27行目を見るとおり、やはりエラーになっていることが分かると思う。しかし、これは移動可能変数は固定できないということではない。28〜31行目のように、クラスのメンバを指定すると固定することができる。
さて、37〜41行目は、List 21-3でも駄目だったが、fixedステートメントを適用してもやはり駄目であった。これは、移動可能変数を固定できないためではなく、fixedステートメントの括弧内がキャストを含む複雑な式になっているためだ。式のデータ型の記述が複雑化すると、固定すべきメモリの正しい位置が分かりにくくなり、うまく機能しないようだ。fixedステートメントを使う場合は、28行目のようなシンプルな式を書くように心掛けるとよいかもしれない。
21-5 配列にポインタ・アクセスする
C/C++では、配列にポインタでアクセスする方法がよく使われていたが、C#でも可能である。List 21-5はそれを記述した例である。
1: using System;
2:
3: namespace Sample005
4: {
5: class Class1
6: {
7: private static int test1( int [] array )
8: {
9: int sum = 0;
10: for( int i=0; i<array.Length; i++ )
11: {
12: sum += array[i];
13: }
14: return sum;
15: }
16: unsafe private static int test2( int [] array )
17: {
18: int sum = 0;
19: fixed( int * pArray = &array[0] )
20: {
21: int * ptr = pArray;
22: for( int i=0; i<array.Length; i++ )
23: {
24: sum += *ptr;
25: ptr++;
26: }
27: }
28: return sum;
29: }
30: [STAThread]
31: static void Main(string[] args)
32: {
33: int [] array = { 2, 4, 3, 1, 5 };
34: int sum1 = test1( array );
35: Console.WriteLine(sum1);
36: int sum2 = test2( array );
37: Console.WriteLine(sum2);
38: }
39: }
40: }
これを実行するとFig.21-6のようになる。
7〜15行目は、「安全でないコード」を使わないメソッドで、16〜29行目は同じ結果を出すように「安全でないコード」を用いて記述した例である。配列は移動可能変数なので、19行目のfixedステートメントで一時的に固定している。20〜27行目の範囲内なら、ポインタ変数のpArrayを通じて、配列にアクセスすることができる。よく見ると、19行目で指定されているのは、配列の0番目の要素だけである。しかし、配列は1つの塊としてメモリ上に確保されているので、1個の要素を固定すればすべての要素も同時に固定されるのである。25行目のように、ポインタにも++や--演算子が使用できる。
Copyright© Digital Advantage Corp. All Rights Reserved.