C#の変数の内部表現を確認する

普段使用しているCPUは、最近はMacもインテルのCPUなので、バイトオーダーは「リトルエンディアン」が主流です。

エンディアンの詳細はさておき、つまり、2バイトデータの中身が、「0x 12 34」となっていた場合メモリのレイアウトは 「0x 34 12 」と逆順になっていることを意味します。

つまり普段使用してるC#もリトルエンディアンで動作している事となり、変数の内容は上記規則に従っています。

が しかし、本当にそうなっているか気になったので確認し見ました。

確認用コード

以下コードでは、各サイズの変数を対象にリトルエンディアンになっているかどうかを確認しています。

いったんポインターに変換してからバイト配列にコピーしてバイトオーダーが大きいほうからコンソールに出力するプログラムになっています。

using System;
using System.Linq;
using System.Runtime.InteropServices;

public class AppMain
{
    internal static void Main(string[] args)
    {
        // 整数系の表示

        byte _b = 0x12; // 1byteなので結果は〃
        ShowBytes("byte   ", ToByte(_b));
        // > byte : 0x 12

        short _s = 0x1234;
        ShowBytes("short  ", ToByte(_s));
        // > short : 0x 34 12

        // charだけ、Marshal.SizeOf = 1, sizeof(_c) = 2で値が変わるためちょっと変な感じに...
        char _c = '\x1234';
        ShowBytes("char   ", ToByte(_c));
        // > char : 0x 3f

        int _i = 0x12345678;
        ShowBytes("int    ", ToByte(_i));
        // > int : 0x 78 56 34 12

        long _l = 0x123456789abcdfff;
        ShowBytes("long   ", ToByte(_l));
        // > long : 0x ff df bc 9a 78 56 34 12

        // 浮動小数点
        //  → 符号、指数部、仮数部からなる内部表現のため綺麗には出ない

        float _f = 0.123456f;
        ShowBytes("float  ", ToByte(_f));
        // > float : 0x 80 d6 fc 3d

        double _d = 0.123456; // サイズが違うので同値でもfloatとは一致しない
        ShowBytes("double ", ToByte(_d));
        // > double : 0x bf b6 7e fa cf 9a bf 3f

        Console.ReadLine();
    }

    // 結果をコンソールへ表示
    public static void ShowBytes(string msg, byte[] bs)
    {
        Console.Write(msg + " : 0x ");
        bs.ToList().ForEach(i => Console.Write(i.ToString("x") + " "));
        Console.WriteLine("");
    }


    // 構造体 → バイナリ変換処理
    public static byte[] ToByte<T>(T target) where T : struct
    {
        IntPtr pTarget = IntPtr.Zero;
        int targetSize = Marshal.SizeOf(typeof(T)); // 構造体のサイズ(byte単位)

        try
        {
            pTarget = Marshal.AllocHGlobal(targetSize);
            byte[] bs = new byte[targetSize]; // コピー先の領域を確保

            Marshal.StructureToPtr(target, pTarget, false); // 一旦ポインタに変換してからメモリをコピーする
            Marshal.Copy(pTarget, bs, 0, targetSize);

            return bs;
        }
        finally
        {
            if (pTarget != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pTarget);
            }
        }
    }
}

出力結果はコメントの通りですが、確かにそのようになっていることが確認できました。

従って、BitConverter.ToInt16(byte[]) などの変換メソッドでバイト配列を変換するときは、リトルエンディアン順にバイトを並べないと正しく変換できません。