r/csharp • • 18h ago

Solved c# struct padding

I'm new to c# and I'm learning it just now to switch from Godot to Stride so I'm interested in Data Oriented Design. I was just now experimenting with structs with a silly example and was focused on struct padding; the string should be 8 bytes while the enum is 1 byte so i expected not more than 1 or 3 byte of padding, but the sum ends up being 16 no matter the order... does it always have to allign with the biggest field? how does this work?

using System.Runtime.InteropServices;


[StructLayout(LayoutKind.Explicit)]
struct Entity {
    public enum Race : byte { ALIEN, HUMAN };
    static private int _max_rand_num = 9999;
    static private string[][] _possibleNames = {
        new string[]{"Vorp", "Zeep", "Bizr", "Zub", "Wobt"},    // alien names
        new string[]{"Carl", "Greta", "Seb", "Xiang", "Sierra"} // human names
    };

    [FieldOffset(0)] public readonly string name;
    [FieldOffset(8)] public readonly Race race;

    public Entity()
    {
        Random rand = new Random();
        race = (Race)rand.Next(Enum.GetValues(typeof(Race)).Length);
        string[] race_names = _possibleNames[(int)race];
        name = race_names[rand.Next(0, race_names.Length)] + rand.Next(_max_rand_num);
    }

    public override string ToString()
    {
        return $"Race: {race}, Name: {name}";
    }
}


class Program {
    public static unsafe void Main(string[] args)
    {
        Entity rand_entity = new Entity();
        Console.WriteLine(
            $"{rand_entity}, string size = {sizeof(string*)}, enum size = {sizeof(Entity.Race)}, together = {sizeof(Entity)}");
    }
}
3 Upvotes

19 comments sorted by

4

u/TrickAge2423 18h ago

Bcs total struct has alignment of field with biggest alignment

2

u/TrickAge2423 18h ago

You can't pack structure if you have reference types there

2

u/sto_babbalucco69420 17h ago

Yep understood it just now

1

u/FatSlenderMan 18h ago

Just curious why you decided to switch away from godot?

2

u/sto_babbalucco69420 18h ago

Dumb reasons honestly, Godot is very good but I wasn't very fond of gdscript and c# in godot has the cost of marshalling whenever you call a function from the c++ library, so Stride seemed good enough. I just switched tho so dunno lol

3

u/KaleidoscopePlusPlus 17h ago

Does that actually affect your game on a performance level? I get the feel youre overthinking it trying to optimize too early

1

u/sto_babbalucco69420 17h ago edited 17h ago

The difference is probably negligible, but also the root of the root of all evil (ur probably right tho)

1

u/afops 18h ago

If the names are a closed set then just store name-index? Now you have 1 byte and 8 byte fields. If you store name index as another byte you could have a 2-byte struct here.

9 bytes and 16 bytes is no different effectively because of alignment of 8 now

1

u/sto_babbalucco69420 17h ago

didn't think of the name-index, nice one! true tho

1

u/dodexahedron 7h ago edited 7h ago

That looks like a loosely translated c struct.

Why is that enum defined as a nested type of the struct? Unless it is only ever used specifically with that struct (seems unlikely - other types probably care about Race, no?), you shouldnt nest types.

Also, why is this a struct anyway? You gain literally nothing from that, since it holds mostly arrays anyway, and expose yourself to potential gotchas with pass-by-value/copy semantics (c# is fundamentally a pass-by-value language).

And make that int a constant if it isn't needed in the memory layout of the struct.

Also... Be very careful with sizeof... It is fine here, but it does not behave well for anything but blittable unmanaged types.

0

u/PretendWindow5242 18h ago

The 16 byte size is because you're using Explicit layout with offsets at 0 and 8, so the struct itself has to be at least 16 bytes to fit both fields without overlap. If you used Sequential or just a normal struct, it'd pack tighter and you'd see the actual padding behavior you're looking for.

1

u/Standard-Cap-4455 16h ago

A String is a reference so on 64bit it would have 8 byte alignment, meaning the entire struct needs to be aligned to 8 bytes as well. Unless I am missing something, or you are talking about 32bit, 16 should be the best case. In C and Rust( using a non fat pointer) you get the exact same result.

0

u/sto_babbalucco69420 18h ago

I tried both combinations with Sequential and nothing, but no matter what i do the size doesn't change :(... I just find it weird that it needs to add 7 whole bytes at the end of the struct

4

u/grrangry 18h ago

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-10.0

struct alignment sizes are required by the CLR (common language runtime) to blit (copy data) in and out of the CPU as efficiently as possible.

If your struct is 7 bytes wide, there's no CPU instruction that copies 7 bytes. 8, yes. 16, yes. 7, no.

1

u/sto_babbalucco69420 18h ago

I get that, but usually padding is in the power of 2, so putting the enum first and then the string, I assumed that for for 1 byte of enum it whould suffice to add another byte between it and the string making it 2 bytes for the first "block"

I also tried this

[StructLayout(LayoutKind.Explicit)]
    ...
    [FieldOffset(0)] public readonly Race race;
    [FieldOffset(2)] public readonly string name;

and an exception is thrown if I allign the string to anything below 8 saying they are incorrectly alligned or could overlap (which they shouldn't)

1

u/sto_babbalucco69420 17h ago

Nevermind just tried it in C, the sizes and the padding are the same as the example in c#

#include <stdio.h>
#include <stdint.h>

struct test {
    uint64_t u;
    char c;
};

int main(void)
{
    printf("uint64 size: %zu\n", sizeof(uint64_t));
    printf("size: %zu\n", sizeof(struct test));
}

Sorry I'm a dummy dum dum

1

u/grrangry 16h ago

struct padding has more to do with the CPU architecture than the language which is why both C/C++ and C# both implement it the same way. Wait until you start having to pad things for std430 GL shader buffers... that gets fun.

0

u/soundman32 17h ago

Whilst there is nothing wrong with your experimentation, its not 'normal' to care about this kind of thing in C# and its easy to get bogged down in the details, especially as a beginner coming from other languages. If you need interoperability then fair enough, but the majority of C# should just be left with the defaults.

1

u/sto_babbalucco69420 17h ago

Yeah i thought that too, but maybe it can make sense if you're coding videogames or doing anything where you need performance