連載

C#入門

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

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


固定型と移動型

 &演算子によって変数への参照が得られることは、最初の例で示したとおりである。しかし、どんな変数への参照でも取得できるわけではない。以下は、クラスのインスタンスなど、いくつかの種類の変数に&演算子を適用してみた例である。

 1: using System;
 2:
 3: namespace ConsoleApplication7
 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:       // マネージ型 ('ConsoleApplication7.SampleClass') の変数のアドレスまたはサイズを取得できません。
23:       // マネージ型 ('ConsoleApplication7.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:     static void Main(string[] args)
36:     {
37:       test();
38:     }
39:   }
40: }
いくつかの種類の変数に&演算子を適用したサンプル・プログラム3
コメント・アウトされている部分はコンパイル・エラーとなる組み合わせである。

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

サンプル・プログラム3の実行結果
ローカルな整数型変数への参照とローカルなstructへの参照は、コンパイルして実行することができる。

 コンパイル・エラーになる組み合わせはコメント・アウトしてある。まず、17〜19行目のローカルな整数型変数への参照は問題なく処理されていることが分かる。しかし、20〜24行目でクラスのインスタンスの参照を得ることはコンパイル・エラーになってしまい、取得できない。25〜28行目はローカルなstructへの参照を得ているが、これは正常にコンパイル、実行できる。29〜33行目はstructをボクシングしてインスタンス化しているが、この場合もコンパイラがエラーにしている。

 さて、このようにコンパイルできるものとできないものに分かれた理由は、データを格納するメモリには2つの種類が存在することによる。1つは固定型、もう1つは移動型である。固定型は、.NET Frameworkのメモリ管理機構のメモリ整理によって位置が移動しないもの。移動型は移動してしまう可能性があるものだ。参照を取得できるのは、このうち固定型に限られる。移動型では、参照先が移動してどこかに行ってしまう可能性があり、参照できても正常に処理できないのである。

 さて、ローカル変数はスタック上に確保されるが、スタックは基本的に移動しないものであるため、これに対する参照は問題なく得られる。これが17〜19行目の処理がうまく動く理由だ。次に、20〜24行目もローカル変数のように見えるが、実際のインスタンス本体はスタック外に移動型として確保されるので、これは参照できない。25〜28行目はstructの例だが、クラスと違って、ローカル変数として確保すれば中身のすべてがスタック上に存在するため、参照を取得できる。しかし、29〜33行目のように、structをボクシングするとスタック外に移動型として確保されてしまうので、参照は取得できない。

移動型メモリを参照するfixedステートメント

 移動型メモリへの参照は、何もしなければ得ることができない。しかし、得る方法がないわけではない。fixedステートメントは、移動型メモリを一時的に固定させ、それに対する参照を得られるようにする機能を持つ。以下はそれを用いた例である。

 1: using System;
 2:
 3: namespace ConsoleApplication8
 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:       // マネージ型 ('ConsoleApplication8.SampleClass') の変数のアドレスまたはサイズを取得できません。
23:       // マネージ型 ('ConsoleApplication8.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:     static void Main(string[] args)
44:     {
45:       test();
46:     }
47:   }
48: }
fixedステートメントを用いたサンプル・プログラム4
fixedステートメントにより、移動型であるメモリへのポインタを取得することができる。しかしクラスのインスタンス自体へのポインタやキャストを含む複雑な式ではコンパイル・エラーとなる。

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

サンプル・プログラム4の実行結果
シンプルな式では、インスタンスのメンバ変数へのポインタが取得できる。

 内容は前の例(サンプル・プログラム3)とほぼ同じだが、fixedステートメントを使用するようになっている。

 まず、前の例で駄目だったインスタンスへの参照だが、21〜27行目から分かるとおり、やはりエラーになっている。しかしこれは、移動型は固定できないということではない。ポインタは後で述べるとおり演算子を適用できるが、サイズが確定していなければ処理できない。クラスのサイズが確定できないのは、継承などが行われていると、あるクラスへのポインタが指し示すことができるクラスの種類は1つに限定されないためではないかと思われる。しかし、28〜31行目のように、クラスのメンバを指定すると、固定することができる。28行目で指定されているint型は常にサイズが決まっているのである。

 さて、37〜41行目は、前の例でも駄目だったが、fixedステートメントを適用してもやはり駄目であった。これは、移動型を固定できないためではなく、fixedステートメントの括弧内がキャストを含む複雑な式になっているためだ。式のデータ型の記述が複雑化すると、固定すべきメモリの正しい位置が分かりにくくなり、うまく機能しないようだ。fixedステートメントを使う場合は、28行目のようなシンプルな式を書くように心掛けるとよいかもしれない。


 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 記事ランキング

本日 月間