Який розмір булева в C #? Це дійсно займає 4 байти?


137

У мене є два структури з масивами байтів і булів:

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] values;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public bool[] values;
}

І наступний код:

class main
{
    public static void Main()
    {
        Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
        Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
        Console.ReadKey();
    }
}

Це дає мені такий результат:

sizeof array of bytes: 3
sizeof array of bools: 12

Здається, що асистент booleanзаймає 4 байти. В ідеалі a boolean займе лише один біт ( falseабо true, 0або 1тощо).

Що тут відбувається? Чи booleanсправді тип настільки неефективний?


7
Це одна з найбільш іронічних сутичок у триваючій боротьбі з причин: два чудових відповіді Джона та Ганса просто зробили це, хоча відповіді на це питання, як правило, майже повністю базуються на думках, а не на фактах, посиланнях, або конкретна експертиза.
TaW

12
@TaW: Я здогадуюсь, що закриті голоси були не завдяки відповідям, а оригінальним тоном ОП, коли вони вперше поставили питання - вони чітко мали намір розпочати бійку і прямо це показали у видалених коментарях. Більшу частину суглоба підмілили під килим, але перегляньте історію ревізії, щоб зрозуміти, що я маю на увазі.
BoltClock

1
Чому б не використовувати BitArray?
дед '16

Відповіді:


242

Тип bool має картату історію з багатьма несумісними варіантами між мовними режимами. Це почалося з вибору історичного дизайну, який зробив Денніс Річі, хлопець, який винайшов мову С. У нього не було типу bool , альтернативою був int, коли значення 0 являє собою хибне, а будь-яке інше значення вважається істинним .

Цей вибір був перенесений у Winapi, головна причина використання pinvoke, він має typedef, для BOOLякого є псевдонімом для ключового слова int компілятора C. Якщо ви не застосуєте явний атрибут [MarshalAs], тоді C # bool перетворюється на BOOL, таким чином, створюючи поле, довжиною 4 байти.

Що б ви не робили, ваша структура декларації повинна відповідати вибору часу виконання на мові, з якою ви взаємодієте. Як зазначалося, BOOL для winapi, але більшість C ++ реалізацій обрали байт , більшість інтеропів COM Automation використовує VARIANT_BOOL, що є коротким .

Фактичний розмір C # boolстановить один байт. Сильна проектна мета CLR полягає в тому, що ви не можете цього дізнатися. Макет - це деталь реалізації, який занадто сильно залежить від процесора. Процесори дуже вибагливі до змінних типів та вирівнювання, неправильний вибір може істотно вплинути на продуктивність та призвести до помилок виконання. Роблячи макет нерозкритим, .NET може забезпечити систему універсального типу, яка не залежить від реальної реалізації часу.

Іншими словами, вам завжди потрібно зруйнувати структуру під час виконання, щоб прибити макет. У цей час здійснюється перетворення з внутрішнього макета в макет інтеропа. Це може бути дуже швидким, якщо макет однаковий, повільний, коли поля потрібно переставляти, оскільки це завжди вимагає створення копії структури. Технічний термін для цього є простим , передача структури, що випускається, до нативного коду є швидкою, оскільки маршовий механізм Pinvoke може просто передати покажчик.

Продуктивність також є основною причиною того, що bool - це не один біт. Є небагато процесорів, які роблять біт безпосередньо адресованим, найменша одиниця - байт. Додаткові інструкції потрібно для риби біт з байта, який не спадає безкоштовно. І це ніколи не атомно.

Компілятор C # інакше не соромляться сказати вам, що він займає 1 байт, використовуйте sizeof(bool). Це все ще не фантастичний передбачувач, скільки байтів займає поле під час виконання, CLR також потрібно реалізувати модель пам'яті .NET, і він обіцяє, що прості оновлення змінних є атомними . Для цього потрібні правильні вирівнювання змінних у пам'яті, щоб процесор міг оновлювати її за допомогою одного циклу шини пам'яті. Досить часто bool насправді вимагає 4 або 8 байт в пам'яті через це. Додаткові накладки, які були додані для забезпечення правильного вирівнювання наступного члена.

CLR фактично використовує перевагу, що макет не виявляється, він може оптимізувати компонування класу та переупорядкувати поля, щоб мінімізація прокладки була мінімізована. Так, скажімо, якщо у вас є клас з bool + int + bool, то знадобиться 1 + (3) + 4 + 1 + (3) байти пам'яті, (3) - це прокладка, загалом 12 байт 50% відходів. Автоматичне розташування переставляє на 1 + 1 + (2) + 4 = 8 байт. Тільки клас має автоматичний макет, структури мають за замовчуванням послідовний макет.

Більш м'яко, bool може вимагати аж 32 байти в програмі C ++, складеній із сучасним компілятором C ++, який підтримує набір інструкцій AVX. Якщо вимога вирівнювання має 32 байти, змінна bool може закінчуватися 31 байтом прокладки. Крім того, основна причина, чому .NET тремтіння не видає інструкції SIMD, якщо явно не завернуто, він не може отримати гарантію вирівнювання.



2
Для зацікавленого, але неінформованого читача, ви б пояснили, чи повинен останній абзац читати 32 байти, а не біти ?
Дурний фрік

3
Не впевнений, чому я просто все це прочитав (оскільки мені не потрібно дуже багато деталей), але це захоплююче і добре написано.
Франк V

2
@Silly - це байти . AVX використовує 512 бітні змінні для математики на 8 значеннях з плаваючою точкою за допомогою однієї інструкції. Така 512 бітова змінна вимагає вирівнювання до 32.
Ганс Пасант

3
Оце Так! один пост дав пекло багато тем для розуміння. Ось чому мені подобається просто читати топові запитання.
Чайтанья Гадкарі

151

По-перше, це лише розмір для interop. Він не представляє розмір керованого коду масиву. Це 1 байт на bool- принаймні на моїй машині. Ви можете перевірити це за допомогою цього коду:

using System;
class Program 
{ 
    static void Main(string[] args) 
    { 
        int size = 10000000;
        object array = null;
        long before = GC.GetTotalMemory(true); 
        array = new bool[size];
        long after = GC.GetTotalMemory(true); 

        double diff = after - before; 

        Console.WriteLine("Per value: " + diff / size);

        // Stop the GC from messing up our measurements 
        GC.KeepAlive(array); 
    } 
}

Тепер, коли ви шукаєте масиви з маршовим розміром за значенням, документація говорить:

Коли для властивості MarshalAsAttribute.Value встановлено значення ByValArray, слід встановити поле SizeConst, щоб вказати кількість елементів у масиві. ArraySubTypeПоле може необов'язково містити UnmanagedTypeз елементів масиву , коли необхідно , щоб розрізняти типи рядків. Ви можете використовувати це UnmanagedTypeлише для масиву, елементи якого відображаються як поля в структурі.

Отже, ми дивимось ArraySubType, і на це є документація про:

Ви можете встановити цей параметр на значення з UnmanagedTypeперерахунку, щоб вказати тип елементів масиву. Якщо тип не вказаний, використовується типовий некерований тип, відповідний типу елемента керованого масиву.

Тепер, дивлячись UnmanagedType, є:

Bool
4-байтне булеве значення (true! = 0, false = 0). Це тип Win32 BOOL.

Отже, це за замовчуванням bool, і це 4 байти, тому що це відповідає типу Win32 BOOL - тож якщо ви взаємодієте з кодом, який очікує BOOLмасив, він робить саме те, що ви хочете.

Тепер ви можете вказати ArraySubTypeяк I1натомість, що задокументовано як:

Ціле число, підписане 1 байтом. Ви можете використовувати цей член, щоб перетворити булеве значення в 1-байтовий bool-стиль (true = 1, false = 0).

Тож якщо код, з яким ви взаємодієте, очікує 1 байт на значення, просто використовуйте:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;

Потім ваш код покаже, що як 1 байт на значення, як очікувалося.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.