C/C++の型変換ポインタ操作をC#でもやりたい

# 農工大 Advent Calendar 2024

農工大 Advent Calendar 2024 の 17 日目の記事です。私は農工大について書こうと思いましたがみんな技術系書いているので技術系に変更しました。

# はじめに

C/C++ でできる型変換ポインタ操作を C# でやりたいのでまとめました。

例えば double 型のビット列を整数のビット列として見たいとかありますよね？(あってくれ)

例えば高速逆平方根とかにあります。[wiki参考](https://en.wikipedia.org/wiki/Fast_inverse_square_root)

```c
float Q_rsqrt(float number)
{
	unsigned int i;
	float x2, y;
	const float threehalfs = 1.5F;

	x2 = number * 0.5F;
	y = number;
	i = *(unsigned int *)&y;
	i = 0x5f3759df - (i >> 1);
	y = *(float *)&i;
	y = y * (threehalfs - (x2 * y * y));

	return y;
}
```

`i  = * ( long * ) &y;` `y  = * ( float * ) &i;` この部分です。こんな感じのポインタ操作を以下の3種類 C# でやります。構造体使ってるのはヒープだけでなくスタックでも使えると幸せだからです。

* [float を int32 として扱う](#float-を-int32-として扱う)
* [構造体を別の構造体として見る](#構造体を別の構造体として見る)
* [byte[] と構造体を変換する](#byte[]-と構造体を変換する)

## 環境

* .NET 9
* C# 13.0

# 1. `float` を `int32` として扱う

先ほど示した高速逆平方根の例です。

## `unsafe` を使う

```cs
float a = 10.0f;
Console.WriteLine($"MathF.ReciprocalSqrtEstimate({a}) = {MathF.ReciprocalSqrtEstimate(a)}");
Console.WriteLine($"Q_rsqrt({a}) = {Q_rsqrt(a)}");

unsafe static float Q_rsqrt( float number )
{
    uint i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i = *(uint*)&y;
    i  = 0x5f3759df - ( i >> 1 );
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );

    return y;
}
```

```
MathF.ReciprocalSqrtEstimate(10) = 0.3161621
Q_rsqrt(10) = 0.31568578
```

近似だから誤差があるのは置いといて、 unsafe 使うのはチートすぎるし、プロジェクトの設定変えないと行けないのがめんどい。あと unsafe は Unity で楽に使えない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/5e234506-5012-34cc-3cf8-9439b9a241ba.png)

## `BitConverter` を使う

```cs
float a = 10.0f;
Console.WriteLine($"MathF.ReciprocalSqrtEstimate({a}) = {MathF.ReciprocalSqrtEstimate(a)}");
Console.WriteLine($"Q_rsqrt({a}) = {Q_rsqrt(a)}");

static float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i = BitConverter.SingleToUInt32Bits(y);
    i  = 0x5f3759df - ( i >> 1 );
    y = BitConverter.Int32BitsToSingle((int)i);
    y  = y * ( threehalfs - ( x2 * y * y ) );

    return y;
}
```

`unsafe` 使わないしこれ正解かも

# 2. 構造体を別の構造体として見る

何言ってるんだ？って感じですが、C言語だと以下のようなコードです。別の構造体として見て参照すればメモリ上でちょうど同じ場所のデータを書き換えられます。

```c
#include <stdio.h>

struct int3
{
	int x;
	int y;
	int z;
};

struct int2
{
	int x;
	int y;
};

int main(void)
{
	struct int3 st3 = {1, 2, 3};

	struct int2 *st2 = (struct int2 *)&st3;
	st2->x = 10;
	st2->y = 20;

	printf("st3 = {%d, %d, %d}\n", st3.x, st3.y, st3.z);
	return 0;
}
```

```
st3 = {10, 20, 3}
```

## `System.Runtime.CompilerServices.Unsafe` を使う

```cs
using System.Runtime.CompilerServices;

int3 st3 = new int3 {x = 1, y = 2, z = 3};

ref int2 st2 = ref Unsafe.As<int3, int2>(ref st3);
st2.x = 10;
st2.y = 20;

Console.WriteLine($"st3 = {{{st3.x}, {st3.y}, {st3.z}}}");

struct int3
{
    public int x;
    public int y;
    public int z;
}

struct int2
{
    public int x;
    public int y;
}
```

```
st3 = {10, 20, 3}
```

よさそう。でもこれ Unity じゃ使えないじゃん。

## `StructLayout` で `Union` っぽいもの作って使う

```cs
using System.Runtime.InteropServices;

IntUnion intUnion;
intUnion.st3 = new int3 {x = 1, y = 2, z = 3};
intUnion.st2 = new int2 {x = 10, y = 20};

Console.WriteLine($"st3 = {{{intUnion.st3.x}, {intUnion.st3.y}, {intUnion.st3.z}}}");

struct int3
{
    public int x;
    public int y;
    public int z;
}

struct int2
{
    public int x;
    public int y;
}

[StructLayout(LayoutKind.Explicit)]
struct IntUnion
{
    [FieldOffset(0)]
    public int3 st3;
    
    [FieldOffset(0)]
    public int2 st2;
}
```

```
st3 = {10, 20, 3}
```

`union` いいですよね。以下のようなC言語コードと同じ感じです。

```c
union intunion
{
	struct int3 st3;
	struct int2 st2;
};
```

構造体に `StructLayout` 属性を付けて `LayoutKind.Explicit` を指定すると構造体のメンバーのメモリ上の位置を指定できます。`0`にすることで先頭から始められます。`st3`も`st2`も先頭から始まるようにしてるのでメモリ上の位置を被せています。 

## `Interface` を使う(class なら)

```cs
int3 st3 = new int3 {x = 1, y = 2, z = 3};

int2 st2 = st3;
st2.x = 10;
st2.y = 20;

Console.WriteLine($"st3 = {{{st3.x}, {st3.y}, {st3.z}}}");

class int3 : int2
{
    public int x { get; set; }
    public int y { get; set; }
    public int z;
}

interface int2
{
    public int x { get; set; }
    public int y { get; set; }
}
```

一番C#らしいコードで一番無難なコードだけどクラスだからヒープに確保されちゃうんだよなぁ、、、スタックで扱いたいときもある。ちなみに構造体でやるとインターフェースに代入したときにコピーされます。

# 3. `byte[]` と構造体を変換する

C言語のコードだとこんな感じのことです。ちなみに構造体はポインタです。

```c
#include <stdio.h>

struct int3
{
	int x;
	int y;
	int z;
};

int main(void)
{
	char bytes[12] = "\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00";

	struct int3 *st3p = (struct int3 *)&bytes;
	printf("st3 = {%d, %d, %d}\n", st3p->x, st3p->y, st3p->z);

	return 0;
}
```

```
st3 = {1, 2, 2}
```

逆向きもC言語なら簡単です。

```c
#include <stdio.h>

struct int3
{
	int x;
	int y;
	int z;
};

int main(void)
{
	struct int3 st3 = {1, 2, 3};

	char *p = (char *)&st3;

	printf("p = {%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d}\n", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
	return 0;
}
```

```
p = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0}
```

## `byte[]` → `struct`

### `MemoryMarshal.Cast` で `Span` 経由で使う

```cs
using System.Runtime.InteropServices;

byte[] bytes = [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];

ref int3 st3 = ref MemoryMarshal.Cast<byte, int3>(bytes)[0];

Console.WriteLine($"st3 = {{{st3.x}, {st3.y}, {st3.z}}}");

st3.x = 10;

Console.WriteLine($"bytes = {{{bytes[0]}, {bytes[1]}, {bytes[2]}, {bytes[3]}, {bytes[4]}, {bytes[5]}, {bytes[6]}, {bytes[7]}, {bytes[8]}, {bytes[9]}, {bytes[10]}, {bytes[11]}}}");

struct int3
{
    public int x;
    public int y;
    public int z;
}
```

```
st3 = {1, 2, 3}
bytes = {10, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0}
```

よさそう

`st3.x` に `10` 代入したら `bytes[0]` が `10` になりました。

`MemoryMarshal.Cast` で返ってくるは `Span<int3>` なので構造体の配列でもできます。今回は単体(要素数が1つ)なので`[0]`を指定してます。

## `struct` → `byte[]`

### `MemoryMarshal.CreateSpan` で `Span<int3>` にしてから `MemoryMarshal.AsBytes` で `Span<byte>` にする

```cs
using System.Runtime.InteropServices;

int3 st3 = new int3 { x = 1, y = 2, z = 3 };

var bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref st3, 1));

Console.WriteLine($"bytes = {{{bytes[0]}, {bytes[1]}, {bytes[2]}, {bytes[3]}, {bytes[4]}, {bytes[5]}, {bytes[6]}, {bytes[7]}, {bytes[8]}, {bytes[9]}, {bytes[10]}, {bytes[11]}}}");

bytes[0] = 10;

Console.WriteLine($"st3 = {{{st3.x}, {st3.y}, {st3.z}}}");

struct int3
{
    public int x;
    public int y;
    public int z;
}
```

```
bytes = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0}
st3 = {10, 2, 3}
```

よさそう

`bytes[0]` に `10` を代入したら `st3.x` が `10` になりました。　

`MemoryMarshal.CreateSpan` の第二引数は要素数です。先頭の参照から要素数を指定して`Span<int3>`が作れます。

```cs
int3[] _st3 = new int3[] { st3,st3,st3 };

ref int3 ref_st3 = ref _st3[0];

Span<int3> span = MemoryMarshal.CreateSpan(ref ref_st3, 3);
```

# まとめ

こんな操作は需要がなさそうに見えますが、プログラムの高速化には必要不可欠です。デカいデータを代入したときにコピーが発生したら重くなってしまいます。「ここにデカいデータ置いたから見てくれ～」ってポインタ渡すほうが速いです。特に構造体とかクラスのインスタンスを簡単に高速に`byte[]`に変換できるならファイル保存とか通信のバイナリとして使えるので幸せです。