1

According to this question C# will assign 4 byte size to field of type Fruits whether it is defined like this:

enum Fruits : byte { Apple, Orange, Banana }

or like this:

enum Fruits { Apple, Orange, Banana }

I'm still curious if there is any way of sidesteping this and making the size of enum smaller than 4 bytes. I know that this probably wouldn't be very efficient or desirable but it's still interesting to know if it's possible at all.

  • 4
    in case of collection, say, array - `Fruits[]` you'll get the effect – Dmitry Bychenko Jul 01 '22 at 11:37
  • 1
    @Dmitry really? But why would that happen for a collection but not for an individual field? Can you plaese clarify this to me? – K. A. Kusakov Jul 01 '22 at 11:43
  • 1
    In an array of all the same type padding and alignment isn't an issue. As a field of an object it is. In a `struct` you can use `StructLayout` to explicitly control this. – Jeroen Mostert Jul 01 '22 at 11:57
  • 2
    The above comments are correct. You write `: byte` if you want it to be 8-bit, "logically". Do not worry about whether the runtime will actually layout fields of type `Fruit` with some padding, or if it will actually put a 32-bit or 64-bit value on the call stack, at times. These things happen at the discretion of the runtime, it will likely make a wise choice. The only time when the runtime must not optimize in such ways, is if you use `unsafe` "unmanaged" pointer variables like `Fruit* p`. Note that for a `Fruit f`, the formatting of `$"{f:X}"` can depend on the underlying integer type. – Jeppe Stig Nielsen Jul 01 '22 at 12:17
  • 1
    Is there a reason a 4-byte allocation is considered memory heavy? 64-bit data bus means moving 8-bytes per cycle. Is this for an embedded system, or low throughput application? – John Alexiou Jul 01 '22 at 12:24

3 Answers3

4

Data alignment (typically on 1, 2, 4 byte border) is used for the faster access to the data (int should be aligned on 4 bytes border).

For instance (let me use byte and int instead of enum for readability and struct instead of class - it's an easy way to get size of struct with a help of sizeof):

// sizeof() == 8 == 1 + 3 (padding) + 4
public struct MyDemo {
  public byte A; // Padded with 3 unused bytes 
  public int B;  // Aligned on 4 byte
}

// sizeof() == 8 == 1 + 1 + 2 (padding) + 4
public struct MyDemo {
  public byte A; // Bytes should be aligned on 1 Byte Border 
  public byte B; // Padded with 2 unused bytes
  public int C;  // Aligned on 4 byte
}

// sizeof() == 2 == 1 + 1 
public struct MyDemo {
  public byte A; // Bytes should be aligned on 1 Byte Border 
  public byte B; // Bytes should be aligned on 1 Byte Border 
}

So far so good you can have an effect even in case of fields within class (struct), e.g.

public struct MyClass {
  // 4 Byte in total: 1 + 1 + 2 (we are lucky: no padding here)
  private Fruits m_Fruits; // Aligned on 1 Byte border
  private byte m_MyByte    // Aligned on 1 Byte border
  private short m_NyShort; // Aligned on 2 Byte border
}

In case of a collection (array) all the values are of the same type which should be aligned in the same way, that's why no padding is required:

// Length * 1Byte == Length byte in total
byte[] array = new [] {
  byte1, // 1 Byte alignment
  byte2, // 1 Byte alignment
  byte3, // 1 Byte alignment
  ...
  byteN, // 1 Byte alignment
} 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • Wow, thank you so much for explaining this to me! – K. A. Kusakov Jul 01 '22 at 12:03
  • 1
    There is an interesting quirk - default layout for classes is "Auto" which can have some interesting [effects](https://stackoverflow.com/q/69631438/2501279) in terms of layout. – Guru Stron Jul 01 '22 at 12:21
0

According to this question C# will assign 4 byte size to field of type Fruits whether it is defined like this

I would say that this is not what actually is written there. The post describes the memory alignment on stack which seems to align 4 bytes for byte variable too (can be platform depended):

byte b = 1;

results in the same IL_0000: ldc.i4.1 instruction as the var fb1 = FruitsByte.Apple and int i = 1; (see at sharplab.io) and the same 4 bytes difference (Core CLR 6.0.322.12309 on x86) in the move instructions.

Though using corresponding enum as struct fields will result in them being aligned to corresponding borders:

Console.WriteLine(Unsafe.SizeOf<C>()); // prints 2
Console.WriteLine(Unsafe.SizeOf<C1>()); // prints 8

public enum Fruits : byte { Apple, Orange, Banana }
public enum Fruits1 { Apple, Orange, Banana }
public struct C {
    public Fruits f1;
    public Fruits f2;
}
public struct C1 {
    public Fruits1 f1;
    public Fruits1 f2;
}

The same will happen for arrays, which will allocate continuous region of memory without aligning different elements.

Useful reading:

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • You can discover the "real" layout of `C` and `C1` if you user pointers to these types, like `C* pointer; C1* pointer1;` and so on. Then it matters what it looks like if you cast the pointers to, say `ushort*` or `ulong*`. When you do not use pointers (`unsafe` code), you cannot know if the system really uses some padding between the fields in your `C` and `C1` structs, I guess. – Jeppe Stig Nielsen Jul 01 '22 at 12:32
  • @JeppeStigNielsen it depends. In case of `C` and `C1` as far as I understand we should now - cause they should be [blittable types](https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types) so they have same layout for managed and unmanaged memory. And _"C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default"_ [docs](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-5.0#remarks) – Guru Stron Jul 01 '22 at 12:47
0

For the vast majority of applications the size overhead will not matter at all. For some specialized applications, like image processing, it may make sense to use constant byte values and do bit-manipulations instead. This can also be a way to pack multiple values into a single byte, or combine flag-bits with values:

const byte Apple = 0x01;
const byte Orange= 0x02;
const byte Banana= 0x03;
const byte FruitMask = 0x0f; // bits 0-3 represent the fruit value
const byte Red = 0x10;
const byte Green = 0x20;
const byte ColorMask = 0x70; // bits 4-6 represents color
const byte IsValidFlag = 0x80; // bit 7 represent value flag

...
var fruitValue = myBytes[i] & FruitMask;
var IsRed = (myBytes[i] & ColorMask) == Red ;
var isValid = myBytes[i] & IsValidFlag > 0;
JonasH
  • 28,608
  • 2
  • 10
  • 23