ポインタってなに? どうやって扱うの?
参照情報自体を表示したい
こんにちは、エンジニアでC#ライターの遠藤です!
CやC++で目にする事のあったポインタは、C#でも存在しています。今回の記事では、C#におけるポインタの基礎知識と使い方について紹介します。
この記事は以下のような方へ向けて書きました。
- ポインタについて知りたい方
- C#でポインタが使いたい方
この記事を読んでいただけると、基礎的な知識と使い方が分かります。是非最後までお付き合いください。
ポインタとは
コーディングする中で宣言する変数の値は、メモリに保存されます。そのさいメモリには値を格納する領域があり、そこにはそれぞれアドレスが付けられています。
そのアドレスそのものの値を示す事ができるのがポインタです。
ポインタの基本的な使い方は後ほど解説しますが、基本としては変数に「*(アスタリスク)」をつけたものがポインタとなります。
実際の例:
using System;
class Program
{
public static void Main()
{
unsafe //unsafeブロックの宣言
{
int* p; //ポインタの宣言
int n = 10;
p = &n; //pにnのアドレスを代入している
Console.WriteLine((int)p);
}
}
}
実行結果:
1735908748
※割り振られるアドレスは都度違うので、実行結果は毎回変わります。
ここで一つ注意していただきたいのが、C#ではポインタはCやC++などとは異なり、重大なバグの原因になりやすいことなどから原則としてポインタを使う事は制限されています。
C言語との連携などでどうしても使用したいときは「unsafe」と記述されたブロックの中でだけ使用可能で、くわえてコンパイルのときに/unsafeオプションをつける事が条件となります。
unsafeとは
上述の通り、C#ではポインタをどこでも使えるという訳では無く、unsafeが記述されたブロックだけで使用可能です。
ここではunsafeブロックの書き方を紹介します。
unsafeの使い方
unsafeの基本的な使い方は先ほどの例の通りで、「unsafe{処理}」と記述します。
この処理の中でだけポインタが書けます。
using System;
class Program
{
public static void Main()
{
unsafe //unsafeブロックの宣言
{
int* p; //ポインタの宣言
int n = 10;
p = &n; //pにnのアドレスを代入している
Console.WriteLine((int)p);
}
}
}
unsafe修飾子
unsafeはブロックとするだけでなく、クラスやメソッドをunsafeとすることも可能です。
その時は、unsafe修飾子を付けます。
実際の例:
using System;
class Program
{
unsafe void UnsafeMethod()
{
int* p; //ポインタの宣言
int n = 10;
p = &n; //pにnのアドレスを代入している
Console.WriteLine(*p); //pのアドレスに格納されている値を出力
}
public static void Main()
{
Program test = new Program();
test.UnsafeMethod();
}
}
実行結果:
10
ポインタの使い方
前章のサンプルコードを再度ご確認ください。
11行目で値を出力する際、「*p」と記述されていることが分かりますね。*pは、pのアドレスに格納されている値を意味しています。
ポインタには他とは少し違う操作が必要になりますので、以下の一覧を見て基本的な操作を把握しておきましょう。
演算子 | 意味 | 備考 |
---|---|---|
* | アドレスに格納されている値を参照する(間接参照) | |
-> | ポインタからその構造体のメンバにアクセスする | a->bは(*a).bと同じ意味 |
[] | ポインタにインデックスを付ける | |
& | 変数のアドレスを取得する | |
++ | ポインタのインクリメント | |
-- | ポインタのデクリメント | |
+, - | ポインタ演算をする | |
==、!=、<、>、<=、>= | ポインタを比較する |
fixedステートメントについて
C#にはメモリを整理する処理でガベージコレクション(以降GC)、コンパクションというものがあります。GCが発生すると、その前後でオブジェクトの配置場所が変更されてしまいます。
配置場所が変わってしまうと、当然アドレスが違うので参照ができなくなってしまいます。
これでは困るので、GCでオブジェクトの配置場所が変わってしまうリスクが伴う場合はfixedステートメントを付ける必要が出てきます。
実際の例:
using System;
using System.Runtime.CompilerServices;
class DummyClass
{
public int x;
public int y;
}
class Program
{
unsafe void UnsafeMethod()
{
DummyClass test = new DummyClass();
fixed(int* p = &test.x)
{
//無理やりGCを行う
for (int i = 0; i < 99999999; i++) { var obj_dummy = new object(); }
Console.WriteLine("pのアドレス: " + (int)p);
Console.WriteLine("GC前のメモリ: " + GC.GetTotalMemory(false));
GC.Collect(0, GCCollectionMode.Forced);
Console.WriteLine("+++GCを行います+++");
Console.WriteLine("pのアドレス: " + (int)p);
Console.WriteLine("GC後のメモリ: " + GC.GetTotalMemory(false));
}
}
public static void Main()
{
Program test = new Program();
test.UnsafeMethod();
}
}
実行結果:
pのアドレス: -2092473304 GC前のメモリ: 3242112 +++GCを行います+++ pのアドレス: -2092473304 GC後のメモリ: 119232
GCが発生しても、fixedステートメント内ではアドレスが固定されている事が分かります。
まとめ
今回の記事では、以下のことを解説致しました。
- ポインタとは
- unsafeとは
- ポインタの使い方
- fixedステートメントについて
ポインタは基本的にNGですが、どうしても必要になったケースでは"使わざるを得ない"状態になっていることと思います。
別の方法がなければ覚えるしかないので、ここでC#におけるポインタの基礎的な使い方を覚えて役立ててください。