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

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

その中でも簡単で基本的と思われる部分に関してアウトプットします。
:::
:::note info
ここで扱っているプログラムは.NET9を使って確認しています。
:::

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

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

![2025.1.24.1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976537/ae7f92f2-89f2-c0a3-ea16-3f58364092bb.jpeg)

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

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

// x = F31D57ED18
```

## ポインター型の変数宣言
`型*`を使用することでポインター変数を宣言できます。
```csharp
unsafe
{
    int x;
    int* p = &x;
    Console.WriteLine($"p = {(long)p:X}");
}

// p = 5E0437E9AC
```

## ポインター間接参照演算子
ポインター変数名の前に`*`を付けることでポインターが指すデータにアクセスすることが可能です。
```csharp
unsafe
{
    int x = 100;
    int* p = &x;
    Console.WriteLine($"p  = {(long)p:X}");
    Console.WriteLine($"*p = {*p}");
}

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

また、このアクセスは参照だけでなく、変更も可能です。
```csharp
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
```

## 構造体のメンバーへのアクセス
`->`演算子を使用することで、構造体のメンバーへアクセスすることが可能です。
```csharp
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
```

また、この演算子を使用した場合でも、値の変更は可能です。
```csharp
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
```

この演算子を使わず、`(*構造体).メンバー`という形でも表現できます。
```csharp
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
```

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

```csharp
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*`のデータはアドレスが１バイトずつ、`int*`のデータはアドレスが４バイトずつ移動していることがわかると思います。

----

`Console.WriteLine`メソッド内で加算・減算した場合、１バイトずつ移動します。
これはキャストすると`int*`ならば４バイトずつ移動するため、恐らく何もしないと`void*`になっているのだと思います。
```csharp
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
```


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

```csharp
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
```

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

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

```csharp
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
```

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

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