What's the size and alignment of C# fixed bool array in struct?
Asked Answered
M

2

11

When doing P/Invoke, it is important to make the data layout match.

We can control the layout of struct by using some attribute.

For example:

struct MyStruct 
{
    public bool f;
}

gives a size of 4. While we can tell compiler to make it a 1 byte bool to match C++ type of bool:

struct MyStruct
{
    [MarshalAs(UnmanagedType.I1)]
    public bool f;
}

gives a size of 1.

These make sense. But when I test fixed bool array, I was confused.

unsafe struct MyStruct
{
    public fixed bool fs[1];
}

gives a size of 4 bytes. and

unsafe struct MyStruct
{
    public fixed bool fs[4];
}

still gives a size of 4 bytes. but

unsafe struct MyStruct
{
    public fixed bool fs[5];
}

gives a size of 8.

It looks like in fixed bool array, the size of bool element is still 1 byte, but the alignment is 4 bytes. This doesn't match C++ bool array, which is 1 byte size and alignment.

Can someone explain me on this?

Update : I finally find out, the reason is, bool type in a struct, then that struct will NEVER be blittable! So don't expect a struct which has bool type inside to be same layout as in C.

Regards, Xiang.

Markle answered 7/1, 2016 at 5:20 Comment(0)
A
16

A bool is rather special, it goes back to Dennis Ritchie's decision to not give the C language a bool type. That caused plenty of mayhem, language and operating system designers added it themselves and made incompatible choices.

It was added to the Winapi as the BOOL typedef. That's the default marshaling if you don't force another type. Typedef-ed as int to keep it compatible with C, takes 4 bytes as you found out. And aligns to 4, as you found out, like any int does.

It was added to C++. Without a size specification, most C++ compiler implementations chose a single byte for storage. Most notably the Microsoft C++ compiler did, the most likely implementation you'll interop with.

It was added to COM Automation as VARIANT_BOOL. Originally targeted as the new extension model for Visual Basic to get rid of the VBX restrictions, it became wildly popular and just about any language runtime on Windows now supports it. VB back then was heavily affected by 16-bit operating system sensibilities, a VARIANT_BOOL takes 2 bytes.

All three native runtime environments are likely targets for interop in a C# program. Clearly the CLR designers had a very difficult choice to make, having to pick between 1, 2 and 4 bytes. There is no way to win, while the CLR does have a shot at guessing at COM interop, it cannot know whether you try to interop with a C-based api or a C++ program. So they made the only logical choice: none of them.

A struct or class type that contains a bool is never blittable. Not even when you apply [MarshalAs(UnmanagedType.U1)], the one that would make it compatible with the CLR type. Not so sure that was a good decision, it however was the one they made so we'll have to deal with it.

Getting a blittable struct is highly desirable, it avoids copying. It allows native code to directly access the managed heap and stack. Pretty dangerous and many a broken pinvoke declaration has corrupted the GC heap without the usual benefit of the unsafe keyword alert. But impossible to beat for speed.

You get a blittable struct by not using bool. Use byte instead. You can still get the bool back by wrapping the struct member with a property. Don't use an auto-implemented property, you must care about the position of the byte. Thus:

struct MyStruct 
{
    private byte _f;
    public bool f {
        get { return _f != 0; }
        set { _f = value ? 1 : 0; }
    }
}

Native code is oblivious to the property. Don't fret about runtime overhead for the getter and setter, the jitter optimizer makes them disappear and they turn into a single CPU instruction each.

Aloisius answered 7/1, 2016 at 7:41 Comment(5)
private bool _f; -> private byte _f;?Excerpta
Glad I explained it well :) Thanks.Aloisius
Thanks, that was a very detailed explanation. Although I understand a property in C++ might be a robust solution, but I still want to know why in C# fixed bool array, it behaviors very strange, looks like its size is 1 byte, but its alignment is 4 bytes. Do you know what magic is behind the C# marshaling on fixed bool array embedded in the struct?Markle
Fixed buffers are pretty strange animals in CLR land, it is treated as a custom value type with no members. Just a blob of memory. Explains the strong limitation of the type of the buffer element, the compiler needs to know the size at compile-time so it can properly index the blob. Only simple value types will do. A lot more relevant to the C++/CLI language, such a value type can store a native C++ object. Which is the probable reason for the alignment choice. Ideally it would align to 8, like C++ objects do, but the 32-bit CLR cannot provide that guarantee, it only supports 4.Aloisius
But I checked unsafe struct { fixed short f[1] }, that is of size 2, and { fixed byte f[1] }, that is of size 1. All those numeric types as fixed array works as expected, except boolean type. I think bool should also be very basic type, it can be of 4byte or 1 byte, but from my observation, it is size 1 and align 4, that is quite odd. Anyway, thanks for your explanation.Markle
M
-1

Should work:

[StructLayout(LayoutKind.Sequential)]
unsafe struct MyStruct
{
   public fixed bool fs[5];
}
Mcfadden answered 7/1, 2016 at 18:2 Comment(2)
I tested, even I added this sequential layout attribute, bool[3] size=4, bool[4] size=4, bool[5] size=8, same.Markle
Me too:) Did you try [StructLayout(LayoutKind.Sequential, Pack = 1)] ? Looks like somewhere in the project you have an option that sets the default alignment for structs to 4 bytesMcfadden

© 2022 - 2024 — McMap. All rights reserved.