Ответ 1
Тип bool имеет клетчатую историю со многими несовместимыми выборами между языковыми режимами. Это началось с исторического выбора дизайна, сделанного Деннисом Ритчи, парнем, который изобрел C-язык. У него не было типа bool, альтернативой было int, где значение 0 представляет значение false, а любое другое значение считается истинным.
Этот выбор был перенесен в Winapi, основной причиной использования pinvoke, он имеет typedef для BOOL
, который является псевдонимом для ключевого слова int compiler. Если вы не примените явный атрибут [MarshalAs], тогда С# bool преобразуется в BOOL, создавая поле длиной 4 байта.
Независимо от того, что вы делаете, ваше объявление структуры должно соответствовать совпадению с выбором времени выполнения, выполняемым на языке, с которым вы взаимодействуете. Как уже отмечалось, BOOL для winapi, но большинство реализаций на С++ выбрали байты, большинство COM Automation interop использует VARIANT_BOOL, который является коротким.
Фактический размер С# BOOL
- один байт. Сильная цель дизайна CLR заключается в том, что вы не можете узнать. Макет - это деталь реализации, которая слишком сильно зависит от процессора. Процессоры очень разборчивы в отношении типов переменных и выравнивания, неправильный выбор может существенно повлиять на производительность и вызвать ошибки времени выполнения. Сделав макет неоткрываемым,.NET может обеспечить универсальную систему типов, которая не зависит от реальной реализации выполнения.
Другими словами, вы всегда должны маршировать структуру во время выполнения, чтобы приглушить макет. В это время выполняется преобразование из внутренней компоновки в макет interop. Это может быть очень быстрым, если макет идентичен, медленный, когда поля необходимо переустановить, поскольку для этого всегда требуется создать копию структуры. Технический термин для этого является blittable, передача blittable struct в собственный код выполняется быстро, потому что маркерщик pinvoke может просто передать указатель.
Производительность также является основной причиной того, что bool не является одним битом. Существует несколько процессоров, которые делают бит непосредственно адресуемым, а наименьший - байтом. Требуется дополнительная инструкция, чтобы выловить бит из байта, который не приходит бесплатно. И он никогда не является атомарным.
Компилятор С# не стесняется рассказывать вам, что он занимает 1 байт, используйте sizeof(bool)
. Это все еще не фантастический предсказатель для того, сколько байтов занимает поле во время выполнения, для CLR также необходимо реализовать модель памяти .NET, а promises - то, что простые обновления переменных являются атомарными. Это требует, чтобы переменные были правильно выровнены в памяти, чтобы процессор мог обновить его с помощью одного цикла шины памяти. Из-за этого довольно часто для bool на самом деле требуется 4 или 8 байтов. Дополнительное дополнение, которое было добавлено для обеспечения правильного выравнивания следующего элемента.
CLR на самом деле использует возможность того, что макет не может быть распознан, он может оптимизировать макет класса и перенастроить поля, чтобы скрыть его. Итак, скажем, если у вас есть класс с членом bool + int + bool, тогда он будет принимать 1 + (3) + 4 + 1 + (3) байты памяти, (3) является дополнением, в общей сложности 12 байт. 50% отходов. Автоматическая компоновка перестраивается до 1 + 1 + (2) + 4 = 8 байтов. Только класс имеет автоматическую компоновку, по умолчанию структуры имеют последовательный макет.
Более мрачно, bool может потребовать 32 байта в программе на С++, скомпилированной с помощью современного компилятора С++, который поддерживает набор инструкций AVX. Что требует 32-байтового требования к выравниванию, переменная bool может закончиться 31 байтом заполнения. Кроме того, основная причина, по которой .NET-джиттер не выводит SIMD-инструкции, если явно не упакован, он не может получить гарантию выравнивания.