連載

C#入門

第21回 ポインタを使用できるunsafe

(株)ピーデー
川俣 晶
2002/02/16


配列にポインタ・アクセスする

 C/C++では、配列にポインタでアクセスする方法がよく使われていたが、C#でも可能である。以下はそれを記述した例である。

 1: using System;
 2:
 3: namespace ConsoleApplication1
 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:     static void Main(string[] args)
31:     {
32:       int [] array = { 2, 4, 3, 1, 5 };
33:       int sum1 = test1( array );
34:       Console.WriteLine(sum1);
35:       int sum2 = test2( array );
36:       Console.WriteLine(sum2);
37:     }
38:   }
39: }
配列をポインタでアクセスするサンプル・プログラム5
ポインタによる配列のアクセスでは、配列の先頭の要素へのポインタを取得し、そのポインタを進めることによって各要素の値にアクセスすることができる。

 これを実行すると以下のようになる。

サンプル・プログラム5の実行結果
インデックスとポインタによる2通りのアクセス方法で、配列の各要素の値の合計値を求め表示している。

 7〜15行目は、unsafeコードを使わないメソッドで、16〜29行目は同じ結果を出すようにunsafeコードを用いて記述した例である。配列は移動型なので、19行目のfixedステートメントで一時的に固定している。20〜27行目の範囲内なら、ポインタ変数をpArrayを通じて、配列にアクセスすることができる。よく見ると、19行目で指定されているのは、配列の0番目の要素だけである。しかし、配列は1つの塊として、物理的に連続したメモリ上に確保されているので、1個の要素を固定すればすべての要素も同時に固定されるのである。25行目のように、ポインタにも“++”や“--”演算子が使用できる。

ポインタのサイズ

 ポインタには、“++”や“--”演算子が利用できるが、その際、変化する量はデータ型によって異なる。データ型によって占有するサイズが異なるので、“++”演算子で次のデータに進めたとき、進む量を変化させる必要があるためだ。以下は具体的なサイズを、sizeof演算子で調べるサンプル・ソースである。

 1: using System;
 2: using System.Runtime.InteropServices;
 3:
 4: namespace ConsoleApplication10
 5: {
 6:   [StructLayout(LayoutKind.Sequential,Pack=1)]
 7:   struct SampleStruct1
 8:   {
 9:     public int x;
10:     public sbyte y;
11:   }
12:   [StructLayout(LayoutKind.Sequential,Pack=2)]
13:   struct SampleStruct2
14:   {
15:     public int x;
16:     public sbyte y;
17:   }
18:   [StructLayout(LayoutKind.Sequential,Pack=4)]
19:   struct SampleStruct3
20:   {
21:     public int x;
22:     public sbyte y;
23:   }
24:   class Class1
25:   {
26:     unsafe static void test()
27:     {
28:       Console.WriteLine( sizeof(int) );
29:       Console.WriteLine( sizeof(short) );
30:       Console.WriteLine( sizeof(sbyte) );
31:       Console.WriteLine( sizeof(SampleStruct1) );
32:       Console.WriteLine( sizeof(SampleStruct2) );
33:       Console.WriteLine( sizeof(SampleStruct3) );
34:     }
35:     static void Main(string[] args)
36:     {
37:       test();
38:     }
39:   }
40: }
ポインタのサイズを求めるサンプル・プログラム6
ポインタのサイズはsizeof演算子を使用して求めることができる。structへのポインタに対してはStructLayout属性でメンバの値を配置するサイズを指定することができる。

 これを実行すると以下のようになる。

サンプル・プログラム6の実行結果
intのサイズは4bytes、shortのサイズは2bytes、sbyteのサイズは1byteとなった。structの場合には同じメンバの構成であってもStructLayout属性での指定によってサイズが異なる。

 まず表示内容の最初の3行は簡単に分かるだろう。intのサイズは4bytes、shortのサイズは2bytes、sbyteのサイズは1byteであることを示している。つまり、“++”演算子で、int型のポインタなら4個進む、というわけだ。問題は後半の3つの数値だろう。これは、すべて同じ内容の3つのstructのサイズを表示しているが、すべて結果が異なっている。これは、StructLayout属性のPack引数の指定により変化したものだ。Pack引数は、メンバの値を配置する単位を指定する。“Pack=4”なら、4bytesごとに整列させられるので、1byteで済むsbyteも、4bytes分の場所を占有してしまい、struct全体では8bytesを消費してしまっている。一方、“Pack=1”なら1byteごとに配列するので、単純にint型(4)とsbyte型(1)で計5bytesという結果になるわけである。


 INDEX
  第21回 ポインタを使用できるunsafe
    1.unsafeコードとは何か
    2.固定型と移動型
  3.配列にポインタ・アクセスする
    4.ポインタを演算する
    5.本当にunsafeコードが欲しくなる事
 
「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間