Der Bool- Typ hat eine wechselvolle Historie mit vielen inkompatiblen Auswahlmöglichkeiten zwischen Sprachlaufzeiten. Dies begann mit einer historischen Designentscheidung von Dennis Ritchie, dem Erfinder der C-Sprache. Es gab keinen Bool- Typ, die Alternative war int, wobei ein Wert von 0 falsch darstellt und jeder andere Wert als wahr angesehen wurde .
Diese Auswahl wurde in Winapi übernommen, dem Hauptgrund für die Verwendung von Pinvoke. Es hat ein typedef, für BOOL
das ein Alias für das Schlüsselwort int des C-Compilers ist . Wenn Sie ein explizites [MarshalAs] Attribut nicht gelten dann ein C # Bool auf eine BOOL umgewandelt wird, wodurch ein Feld erzeugt , das 4 Bytes lang ist.
Was auch immer Sie tun, Ihre Strukturdeklaration muss mit der Laufzeitauswahl in der Sprache übereinstimmen, mit der Sie interagieren. Wie bereits erwähnt, BOOL für den winapi aber die meist C ++ Implementierungen wählte Byte , die meisten COM Automation Interop verwendet VARIANT_BOOL die eine ist kurz .
Die tatsächliche Größe eines C # bool
beträgt ein Byte. Ein starkes Designziel der CLR ist, dass Sie es nicht herausfinden können. Das Layout ist ein Implementierungsdetail, das zu stark vom Prozessor abhängt. Prozessoren sind sehr wählerisch in Bezug auf Variablentypen und Ausrichtung. Falsche Entscheidungen können die Leistung erheblich beeinträchtigen und Laufzeitfehler verursachen. Indem das Layout nicht entdeckt werden kann, kann .NET ein universelles Typsystem bereitstellen, das nicht von der tatsächlichen Laufzeitimplementierung abhängt.
Mit anderen Worten, Sie müssen zur Laufzeit immer eine Struktur marshallen, um das Layout festzulegen. Zu diesem Zeitpunkt erfolgt die Konvertierung vom internen Layout zum Interop-Layout. Dies kann sehr schnell sein, wenn das Layout identisch ist, langsam, wenn Felder neu angeordnet werden müssen, da dafür immer eine Kopie der Struktur erstellt werden muss. Der Fachbegriff hierfür ist blittable . Das Übergeben einer blittable Struktur an nativen Code ist schnell, da der Pinvoke-Marshaller einfach einen Zeiger übergeben kann.
Leistung ist auch der Hauptgrund, warum ein Bool kein einzelnes Bit ist. Es gibt nur wenige Prozessoren, die ein Bit direkt adressierbar machen. Die kleinste Einheit ist ein Byte. Eine zusätzliche Anweisung ist erforderlich, um das Bit aus dem Byte zu fischen, das nicht kostenlos ist. Und es ist niemals atomar.
Der C # -Compiler scheut sich nicht, Ihnen mitzuteilen, dass es 1 Byte dauert sizeof(bool)
. Dies ist immer noch kein fantastischer Prädiktor für die Anzahl der Bytes, die ein Feld zur Laufzeit benötigt. Die CLR muss auch das .NET-Speichermodell implementieren und verspricht, dass einfache Variablenaktualisierungen atomar sind . Dies erfordert, dass die Variablen im Speicher richtig ausgerichtet sind, damit der Prozessor sie mit einem einzigen Speicherbuszyklus aktualisieren kann. Ziemlich oft benötigt ein Bool aus diesem Grund tatsächlich 4 oder 8 Bytes Speicher. Zusätzliche Polsterung, die hinzugefügt wurde, um sicherzustellen, dass das nächste Element richtig ausgerichtet ist.
Die CLR nutzt den Vorteil, dass das Layout nicht entdeckt werden kann. Sie kann das Layout einer Klasse optimieren und die Felder neu anordnen, sodass die Auffüllung minimiert wird. Wenn Sie also eine Klasse mit einem bool + int + bool-Mitglied haben, würde es 1 + (3) + 4 + 1 + (3) Bytes Speicher benötigen, (3) ist das Auffüllen für insgesamt 12 Bytes. 50% Abfall. Das automatische Layout wird auf 1 + 1 + (2) + 4 = 8 Byte umgestellt. Nur eine Klasse hat ein automatisches Layout, Strukturen haben standardmäßig ein sequentielles Layout.
Noch düsterer ist, dass ein Bool in einem C ++ - Programm, das mit einem modernen C ++ - Compiler kompiliert wurde, der den AVX-Befehlssatz unterstützt, bis zu 32 Byte benötigen kann. Was eine 32-Byte-Ausrichtungsanforderung auferlegt, kann die Bool-Variable mit 31 Bytes Auffüllen enden. Auch der Hauptgrund, warum ein .NET-Jitter keine SIMD-Anweisungen ausgibt, kann die Ausrichtungsgarantie nicht erhalten, es sei denn, er ist explizit verpackt.