0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#のポインターの基本について学んだ事のアウトプット

Posted at

スタックとヒープで学んだ事をアウトプットしようと思っていたのですが、ポインター操作が必要になりましたので、その前にポインターについて学んだ事をアウトプットしていきたいと思います。

その中でも簡単で基本的と思われる部分に関してアウトプットします。

ここで扱っているプログラムは.NET9を使って確認しています。

確認前の設定

今回、記事ではポインターを操作するため、unsafeを使用します。
unsafeを使用するためには設定ファイルを変更する必要があります。

ビルド→全般→アンセーフコードにチェックを入れます。

2025.1.24.1.jpg

アドレス取得演算子

&演算子を使用することで、アドレスを取得することが可能です。

unsafe
{
    int x;
    Console.WriteLine($"x = {(long)&x:X}");
}

// x = F31D57ED18

ポインター型の変数宣言

型*を使用することでポインター変数を宣言できます。

unsafe
{
    int x;
    int* p = &x;
    Console.WriteLine($"p = {(long)p:X}");
}

// p = 5E0437E9AC

ポインター間接参照演算子

ポインター変数名の前に*を付けることでポインターが指すデータにアクセスすることが可能です。

unsafe
{
    int x = 100;
    int* p = &x;
    Console.WriteLine($"p  = {(long)p:X}");
    Console.WriteLine($"*p = {*p}");
}

// p  = 62BDF7EA3C
// *p = 100

また、このアクセスは参照だけでなく、変更も可能です。

unsafe
{
    int x = 100;
    int* p = &x;
    Console.WriteLine($"p  = {(long)p:X}");
    Console.WriteLine($"*p = {*p}");

    *p = 200;
    Console.WriteLine($"*p = {*p}");
    Console.WriteLine($"x  = {x}");
}

// p  = AAC157EAFC
// *p = 100
// *p = 200
// x  = 200

構造体のメンバーへのアクセス

->演算子を使用することで、構造体のメンバーへアクセスすることが可能です。

class Program
{
    struct S
    {
        public int X;
        public int Y;
        public int Z;
    }

    public static void Main()
    {
        unsafe
        {
            S s = new() { X = 100, Y = 200, Z = 300 };
            S* p = &s;
            Console.WriteLine($"p->X = {p->X}");
            Console.WriteLine($"p->Y = {p->Y}");
            Console.WriteLine($"p->Y = {p->Z}");
        }
    }
}

// p->X = 100
// p->Y = 200
// p->Y = 300

また、この演算子を使用した場合でも、値の変更は可能です。

class Program
{
    struct S
    {
        public int X;
        public int Y;
        public int Z;
    }

    public static void Main()
    {
        unsafe
        {
            S s = new() { X = 100, Y = 200, Z = 300 };
            S* p = &s;
            Console.WriteLine($"p->X(1) = {p->X}");
            p->X = 500;
            Console.WriteLine($"p->X(2) = {p->X}");
        }
    }
}

// p->X(1) = 100
// p->X(2) = 500

この演算子を使わず、(*構造体).メンバーという形でも表現できます。

class Program
{
    struct S
    {
        public int X;
        public int Y;
        public int Z;
    }

    public static void Main()
    {
        unsafe
        {
            S s = new() { X = 100, Y = 200, Z = 300 };
            S* p = &s;
            Console.WriteLine($"(*p).X = {(*p).X}");
            Console.WriteLine($"(*p).Y = {(*p).Y}");
            Console.WriteLine($"(*p).Z = {(*p).Z}");
        }
    }
}

// (*p).X = 100
// (*p).Y = 200
// (*p).Z = 300

ポインター変数の加算・減算

ポインター変数を加算・減算することにより、指しているアドレスの位置を移動させることが可能です。
移動する距離は変数のバイト数分です。

unsafe
{
    byte b1;
    byte* p1 = &b1;
    byte* p2 = p1 + 1;
    byte* p3 = p1 + 2;

    Console.WriteLine($"p1 = {(long)p1}");
    Console.WriteLine($"p2 = {(long)p2}");
    Console.WriteLine($"p3 = {(long)p3}");

    Console.WriteLine();

    int i1;
    int* p4 = &i1;
    int* p5 = &i1 + 1;
    int* p6 = &i1 + 2;
    Console.WriteLine($"i1 = {(long)p4}");
    Console.WriteLine($"i2 = {(long)p5}");
    Console.WriteLine($"i3 = {(long)p6}");

}

// p1 = 974501964588
// p2 = 974501964589
// p3 = 974501964590

// i1 = 974501964556
// i2 = 974501964560
// i3 = 974501964564

アドレスがbyte*のデータはアドレスが1バイトずつ、int*のデータはアドレスが4バイトずつ移動していることがわかると思います。


Console.WriteLineメソッド内で加算・減算した場合、1バイトずつ移動します。
これはキャストするとint*ならば4バイトずつ移動するため、恐らく何もしないとvoid*になっているのだと思います。

unsafe
{
    int x;
    int*p = &x;
    Console.WriteLine($"p(0) = {(long)p}");
    Console.WriteLine($"p(1) = {(long)p + 1}");
    Console.WriteLine($"p(2) = {(long)p + 2}");
    Console.WriteLine();
    Console.WriteLine($"p(1) = {(long)(int*)(p + 1)}");
    Console.WriteLine($"p(2) = {(long)(int*)(p + 2)}");
}

// p(0) = 200782900636
// p(1) = 200782900637
// p(2) = 200782900638

// p(1) = 200782900640
// p(2) = 200782900644

ポインターの比較演算子

ポインターも比較演算子を使用することで比較が可能です。
以下は宣言した変数とそれの位置をプラス方向、マイナス方向に移動したポインターのアドレスと比較をしています。

unsafe
{
    long x;
    long y;
    long* p1 = &x - 1;
    long* p2 = &y + 1;

    Console.WriteLine($"(y == p1) = {&y == p1}");
    Console.WriteLine($"(x == p2) = {&x == p2}");
}

// (y == p1) = True
// (x == p2) = True

するとアドレスが同じ位置を指すため、一致しました。

ポインター要素アクセス演算子

[]演算子を使っても特定の位置のアドレスのデータを参照することが可能です。
添え字で指定した位置は各型のバイト数分進めた位置です。

unsafe
{
    int x = 0xF1;
    int y = 0xF2;
    int z = 0xF3;
    int* p = &z;

    Console.WriteLine($"p[0] = {p[0]:X}, {(long)&p[0]}");
    Console.WriteLine($"p[1] = {p[1]:X}, {(long)&p[1]}");
    Console.WriteLine($"p[2] = {p[2]:X}, {(long)&p[2]}");
}

// p[0] = F3, 106293946740
// p[1] = F2, 106293946744
// p[2] = F1, 106293946748

おわりに

アンセーフなコードやポインター周りの機能はまだまだありますが、この記事ではここまでにしたいと思います。

固定サイズバッファー、関数ポインターなどカロリー高めな感じがしていますので、学べる事ができたらそれぞれ記事にしてアウトプットできたらと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?