Mit C Makros etwas tun, was mit ihnen nicht möglich ist

written by Martin Häcker on

Das Problem ist also ein Makro zu schreiben, das typsicher und nach Typen überladen ist, eine variable Anzahl von Argumenten erhält und zudem einfach und verständlich ist.

Zugegeben, mir ist noch nicht klar wie ich das umsetzen kann.

Aber, wenn man das Problem nur ein klein wenig vereinfacht, und nur versucht eines oder mehrere Argumente zu unterstützen...

Also zum Beispiel ein LOG() Macro, dass als erstes Argument irgend etwas erhalten kann (für den Anfang, irgend etwas das sich in einen String umwandeln lässt) dann erhält man etwas das sich so verwenden lässt:

LOG(@"fnord");
LOG("fnord");
char *string = "fnord";
LOG(string);
LOG(@"%@", @"fnord");
LOG(@"%s", "fnord");
LOG(@"%d", 10);
LOG("%s", "fnord");
LOG(@"%@", [NSString stringWithFormat:@"%s,%s", "fnord", "fnord"]);

Die Implementierung ist allerdings etwas tricky.

Typinvariante Makros wie MAX(x, y) schreibt man üblicherweise so:

#define MAX(x, y) ({ \
    typeof(x) __x = (x); \
    typeof(y) __Y = (y); \
    (__x < __y) ? __x : __y; \
})

Das bedeutet durch die speziellen klammern ({ ... }) wird das Makro wie eine Funktion behandelt (eine GCC-Extension) und die übergebenen Ausdrücke nur einmal auswertet und in einer Variablen vom korrekten Typ speichert. int m = MAX(i++, ++j); ist damit also kein Problem. Schließlich ist der Wert der letzten Zeile der "Rückgabewert" der Makro-Funktion

Loggen ist aber schon noch ein Problem, da mit diesem Trick, der Typ noch nicht herauszufinden ist. Mein erster Ansatz mit __builtin_types_compatible_p(typeof(aValue), char *) etc. brachten mich da nicht weiter. Es braucht noch die @encode() Anweisung von Obj-C. Direkt im Makro mit if (@encode(char *) == @encode(typeof(aValue))) war es dann allerdings auch noch nicht obwohl ich gerne mit __builtin_choose_expr( aConstant, expressionOne, expressionTwo ) zur Compilezeit alles erledigt hätte.

Nun ja, dann eben zur Laufzeit mit @encode().

#define LOG(aValue) [[Log sharedLog] logObjcType:@encode(typeof(aValue)) value:aValue]

Die erzeugt aus einem Typ nämlich einen C-String - der zur Laufzeit geparst werden kann um daraus herauszulesen wie der void-Pointer auf aValue interpretiert werden muss.

Wie ich das gemacht hab - morgen. :)