Ich bin letzte Woche über einen schicken C-Codeschnipsel gestolpert, dessen Quintessenz ich hier gerne im Detail festhalten möchte.Der wesentliche Knackpunkt hier ist der, daß sich mit folgendem Assert-Makro sizeof-Checks bereits zur Compilezeit machen lassen und nicht erst zur Laufzeit passieren:
Hübsch, nicht?#define CONCAT2(X,Y) X##Y
#define CONCAT(X,Y) CONCAT2(X,Y)
#define MYASSERT(expr) \
typedef char CONCAT(Assertion_In_Line_,__LINE__)[(expr)?1:-1]
MYASSERT(42 == sizeof(extVar));
Aber fangen wir mit den leichten Sachen an.
Die beiden ##-Zeichen in Zeile 1 sind ein Operator für den Präprozessor, die lediglich aus den beiden Strings X und Y einen String XY machen. Das function-like Makro CONCAT2 liefert also einen verbundenen String zurück.
In Zeile zwei wird eine Indirektion hinzugefügt, indem CONCAT auf CONCAT2 gemappt wird. Dies ist notwendig, damit der Präprozessor das __LINE__ in Zeile 4 evaluiert, und nicht einfach das "__LINE__" als solches anhängt.
Eine schöne, detaillierte Erklärung für die Notwendigkeit dieser Indirektion gibt es hier.
Das __LINE__ ist ein vordefiniertes Makro und wird automatisch durch die Zeilennummer ersetzt, in der es verwendet wird. Wir verbinden in Zeile 4 also den Bezeichner "Assertion_In_Line_" mit der aktuellen Zeilennummer. Wenn das Assert zuschlägt, sehen wir in der Compiler-Meldung dann die Zeile, in der das passiert ist.
In Zeile 3 wird jetzt unser Assert-Makro definiert. "expr" sollte eine Bedingung sein, die wahr oder falsch zurückliefert. Das wäre in diesem Fall später der sizeof-Vergleich.
Wer es bis jetzt noch nicht erkannt hat: Zeile 4 enthält eine Array-Deklaration.
Das Array hätte in unserem Fall den Namen "Assertion_In_Line_5", weil das __LINE__ wie gesagt durch die Zeile ersetzt und mit CONCAT an den Array-Namen angehängt wird.
Die Größe des Arrays ist nun abhängig von dem übergebenen Ausdruck. Wir haben in den eckigen Klammern eine if-else Anweisung mittels Fragezeichen-Operator. Wenn der Ausdruck wahr ist, wird 1 zurückgegeben, ist er falsch, bekommt das Array eine Größe von -1.
Die Deklaration von Arrays mit negativer Größe ist in C aber nicht erlaubt und daher gibt es an dieser Stelle dann einen Compilerfehler.
Mal angenommen ich hätte jetzt eine externe Variable namens "extVar", die nicht 42 Byte groß wäre, dann würde mir der GCC folgenden Fehler beim Kompilieren geben:
test.c:5: error: size of array ‘Assertion_In_Line_5’ is negativeDer GCC gibt netterweise schon die Zeilennummer selber aus, d.h. man hätte sich das mit dem __LINE__ auch sparen können, aber so nett ist nicht jeder Compiler.
Bleibt zum Schluß noch eine Frage offen: Warum steht da ein typedef und nicht eine ganz normale Array-Deklaration?
Der Grund ist der, daß ein typedef keinen Platz im Kompilat belegt. Wenn das Assert nicht zuschlägt, hätten wir im kompilierten Binary eventuell mehrere Assertion_In_Line_* Arrays mit jeweils einer Größe von einem Byte enthalten. Durch das typedef wird diese Platz-Verschwendung vermieden.



