AusgeBOOLt

Was für ein Drama! Boolsche Auswertung gab es in C natürlich schon immer. Doch als C++ den Typen bool als elementaren Kernsprachen-Typen einführte, machte C diesen Schritt nicht mit, hatte jedoch kurz danach die Idee, dieses Feature als typedef im Header stdbool.h nachzuliefern.

Und heute suchte ich verzweifelt einen seltsamen Bug, der den Stack korrumpierte, der jedoch nur in Visual Studio 2005 und 2008, nicht jedoch im 2017 auftrat.

Und was der Grund? Natürlich der Typ bool.


Nun, der Microsoft Teufel hatte aus Gott weiß welchen Gründen die Compiler-Releases daran gehindert stdbool.h mitauszuliefern und eine “natürliche” Brücke zwischen dem C++ Typen bool und dem C-Nachbau _Bool aufzubauen.

Aus diesem Grund kompilierten viele moderne Bibliotheken nicht mit etwas älteren Microsoft Compilern.

Ein bekannter Workaround dafür ist, den Header stdbool.h selbst bereitzustellen, und eine gängige Variante davon ist:

 1#if defined(__cplusplus)
 2
 3typedef bool        _Bool;
 4
 5#else
 6
 7typedef enum
 8{
 9  false = 0,
10  true = 1
11} _Bool;
12
13typedef _Bool bool;
14
15#endif

So lange man C nur mit C Code betreibt und C++ Code nur mit C++, gibt es damit auch keine Probleme.
Doch wehe, man definiert in C eine Struktur mit bool Membern, die von einer C++ Funktion instanziert wird und dann an eine C Funktion per Pointer übergeben wird, dann tanzen Korruptionen und andere Segmentationfaults durch die Gegend.

Der Grund ist - wenn er erst erkannt wurde - denkbar einfach. Weder C noch C++ definieren im Sprachstandard eine feste Größe für bool und enum, doch die Compilerhersteller tun dies für ihre Implementierung.

Und bei Microsoft ist bool ein Byte groß ohne Alignment, während enum Definitionen einem int gleicht, also 4 Bytes lang sind.

Und schon erhält die gleich C-Struktur zwei unterschiedliche Aufbaupläne und ist in C++ kleiner als in C … somit stimmen die Speicheradressen nicht mehr und die Welt geht unter.

Lösung

Nun, mit der Änderung der stdbool.h Implementierung auf:

 1#if !defined(__cplusplus)
 2
 3typedef enum
 4{
 5  false = 0,
 6  true = 1
 7} _BoolType;
 8
 9typedef unsigend char bool;
10
11#endif
12
13typedef bool        _Bool;

ist das Problem auf binärer Ebene gelöst. _Bool und bool sind damit in C und C++ genau ein Byte groß und austauschbar, und die Zuweisung von enums auf unsigned char wird zumindest vom MSVC im C-Modus nicht (besonders schlimm) angemeckert.


Schon witzig, dass ich jetzt seit Jahren mit dieser alten stdbool Implementierung lebe, einige Fremdbibliotheken damit betreibe und noch nie Probleme damit hatte.
Tja, offenbar, habe ich noch nie bool Werte zwischen C und C++ damit ausgetauscht.
Nun gut, dann wurde das ja auch mal Zeit!


Bonusfrage: Wie kam überhaupt es zu dem Szenario?

Antwort: Windows CE! Für was sonst braucht man denn solche uralten Compiler…


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!