CLR sequential structs layout: aligning and size

2019-01-28 01:36发布

问题:

All structs in C# by default are treated as [StructLayout(LayoutKind.Sequential)]-marked value types. So lets take some number of structs and inspect sizes of this structs:

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

class Foo
{
  struct E { }
  struct S0 { byte a; }
  struct S1 { byte a; byte b; }
  struct S2 { byte a; byte b; byte c; }
  struct S3 { byte a; int b; }
  struct S4 { int a; byte b; }
  struct S5 { byte a; byte b; int c; }
  struct S6 { byte a; int b; byte c; }
  struct S7 { int a; byte b; int c; }
  struct S8 { byte a; short b; int c; }
  struct S9 { short a; byte b; int c; }
  struct S10 { long a; byte b; }
  struct S11 { byte a; long b; }
  struct S12 { byte a; byte b; short c; short d; long e; }
  struct S13 { E a; E b; }
  struct S14 { E a; E b; int c; }
  struct S15 { byte a; byte b; byte c; byte d; byte e; }
  struct S16 { S15 b; byte c; }
  struct S17 { long a; S15 b; }
  struct S18 { long a; S15 b; S15 c; }
  struct S19 { long a; S15 b; S15 c; E d; short e; }
  struct S20 { long a; S15 b; S15 c; short d; E e; }

  static void Main()
  {
    Console.WriteLine("name: contents => size\n");
    foreach (var type in typeof(Foo).GetNestedTypes(BindingFlags.NonPublic))
    {
      var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
      Console.WriteLine("{0}: {2} => {1}", type.Name, Marshal.SizeOf(type),
        string.Join("+", fields.Select(_ => Marshal.SizeOf(_.FieldType))));
    }
  }
}

Output is (the same on x86/x64):

name: contents => size

E:  => 1
S0: 1 => 1
S1: 1+1 => 2
S2: 1+1+1 => 3
S3: 1+4 => 8
S4: 4+1 => 8
S5: 1+1+4 => 8
S6: 1+4+1 => 12
S7: 4+1+4 => 12
S8: 1+2+4 => 8
S9: 2+1+4 => 8
S10: 8+1 => 16
S11: 1+8 => 16
S12: 1+1+2+2+8 => 16
S13: 1+1 => 2
S14: 1+1+4 => 8
S15: 1+1+1+1+1 => 5
S16: 5+1 => 6
S17: 8+5 => 16
S18: 8+5+5 => 24
S19: 8+5+5+1+2 => 24
S20: 8+5+5+2+1 => 24

Looking at this results I can't understand the layout (fields aligning and total size) ruleset CLR used for sequential structs. Can somebody explain me this behavior?

回答1:

All the fields are aligned depending on their type. The native types (int, byte, etc.) are all aligned by their size. For example, an int will always be at a multiple of 4 bytes in, while a byte can be anywhere.

If smaller fields come before an int, padding will be added if necessary to ensure the int is properly aligned to 4 bytes. This is why S5 (1+1+4 = 8) and S8 (1+2+4 = 8) will have padding and end up the same size:

[1][1][ ][ ][4] // S5
[1][ ][ 2  ][4] // S8

Additionally, the struct itself inherits the alignment of its most-aligned field (ie. for S5 and S8, int is the most-aligned field, so both of them have an alignment of 4). Alignment is inherited like this so that when you have an array of structs, all the fields in all the structs will be properly aligned. So, 4+2 = 8.

[4][2][ ][ ] // starts at 0
[4][2][ ][ ] // starts at 8
[4][2][ ][ ] // starts at 16

Notice the 4 is always aligned by 4. Without inheriting from the most-aligned field, every other element in an array would have its int aligned by 6 bytes instead of 4:

[4][2] // starts at 0
[4][2] // starts at 6 -- the [4] is not properly aligned!
[4][2] // starts at 12

This would be very bad because not all architectures allow reading from unaligned memory addresses, and even the ones that do have a (potentially quite large, if on a cache line or page boundary) performance penalty for doing it.

Beyond basic performance, alignment also comes into play with concurrency. The C# memory model guarantees reads/writes of the native types up to 4 bytes wide are atomic, and .NET has atomic features like the Interlocked class. Atomic operations like these boil down to CPU instructions that themselves require aligned memory access to work.

Proper alignment is very important!

You will often see clever native coders keep all of this in mind while laying out their structures, sorting all fields from largest to smallest in an effort to keep padding, and thus struct size, to a minimum.