前書き
この記事は、2023のUnityアドカレの12/23の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい!
はじめに
C#は「マネージド」というだけあって、普段はメモリ上でのふるまいなどはあまり意識しなくて済むようになっています。しかし、もう一つの顔があり、unsafeコンテキストや、Marshal、Unsafeクラスなど、ローレベルまでプログラマが制御できる仕組みも備わっております。
StructLayoutで構造体サイズを指定
構造体がメモリ上でどのようなレイアウトをとるかについては、普段は意識せずともコンパイラやランタイム(JIT)が良しなにしてくれます。しかし、[StructLayout]
属性を使うことでこれを指定することもできます。
例えばこのようにすると、フィールドはbyteが1つだけ、賞味1byteしかないですが、512byteとしてあつかうことができます。
[StructLayout(LayoutKind.Sequential, Size = 512)]
struct Size512
{
byte _;
};
Console.WriteLine($"Size512: {sizeof(Size512)}"); // Size512: 512
サイズ0の構造体
RxのUnit型やVoid型のように、事実上、情報量が0bitの特殊な構造体という需要があります。
struct Size0 {}
Console.WriteLine($"Size512: {sizeof(Size0)}"); // Size0: 1
だめでした。暗黙に1byteが消費されてしまっています。明示的にSizeを指定してみたらどうでしょうか?
[StructLayout(LayoutKind.Sequential, Size = 0)]
struct Size0 {}
Console.WriteLine($"Size512: {sizeof(Size0)}"); // Size0: 1
こちらもだめでした。
C++では?
ほかの言語ではどうでしょうか?
struct NoMember {};
cout << "NoMember: " << sizeof(NoMember) << endl;
// NoMember: 1
C++でも、からの構造体でも1byte消費してしまうようです。
フレキシブル構造体
フレキシブル構造体というのは、構造体の末尾にサイズ0の配列を宣言し、可変長として扱う構造体です。これを使うとsizeofにはサイズ0と認識される構造体が作れるようです。
struct Flexible
{
uint8_t _[0];
};
cout << "Flexible: " << sizeof(Flexible) << endl;
Flexible flexibles[100];
auto first = &flexibles[0];
auto second = first + 5;
cout << "Flexible[100]: " << sizeof(flexibles) << endl;
cout << "flexibles[0]: " << first << endl;
cout << "flexibles[5]: " << second << endl;
// Flexible: 0
// Flexible[100]: 0
// flexibles[0]: 0x7fffd953bfa7
// flexibles[5]: 0x7fffd953bfa7
しかし、そもそも可変長ですから、sizeofに意味なんて無いといえばその通りです。
C#でも可変長構造体でサイズ0に…?
C#では、fixedフィールドを要素数0にすることはできませんでした。コンパイルエラーになります。
[StructLayout(LayoutKind.Sequential)]
unsafe struct Flexible
{
// ERROR:
// Fixed size buffers must have a length greater than zero
fixed byte _[0];
};
まとめ
結論、C#でサイズが0の構造体は作れない。
Unit型のように、ダミーの戻り値として使いたい場合は効率的なようにも見えます。しかし、stdcall(関数呼び出し規約)などでは戻り値用のレジスタを使用し、これは戻り値がvoidでもintでも使われるのであんまり差はなさそうな気がします。(C#だとInitializeで0埋めされるからmov命令一個分だけ異なる??)