Zum Inhalt springen

World of Code

TomTitans Geschichten rund ums Coden

  • Startseite
  • Über mich
  • Blog
  • Kontakt

Kommata in nicht klammerbaren Argumenten von Makros

  • Startseite » Blog » Kommata in nicht klammerbaren Argumenten von Makros
29. Januar 2022
Von TomTitan In Cookbook

Kommata in nicht klammerbaren Argumenten von Makros

In diesem Artikel geht es um ein wenig C Preprocessor Magic. Genauer gesagt um die Verwendung und Verarbeitung von Kommata in nicht klammerbaren Argumenten von Makros. Hier zeige ich euch, wie Ihr mit Kommata in Argumenten umgeht, die aus syntaktischen Gründen nicht geklammert werden können.

Das Problem

Im konkreten Fall stand ich in einem Projekt vor dem Problem, ein statisch deklariertes Array mit einem komplexen Typ per Makro zu initialisieren. Alle Elemente des Arrays sollten mit identischen Werten in der Deklaration initialisiert werden. Die Anzahl der Elemente war jedoch plattformabhängig und wurde vom Build-System während des Kompilierens festgelegt. Für die Initialisierung kam ein generisches Makro zum Einsatz, welches ein plattformspezifisches Makro für die eigentliche Initialisierung aufrief. Als Argument erhielten beide Makros den Initialisierungswert für ein Array Element.

Der ganze Mechanismus funktionierte mit primitiven Datentypen sehr gut. Doch beim Initialisieren eines komplexen Typs knallte es dann. Grund war das notwendige Komma zwischen zwei Werten. Da in einem Initializer nur geschweifte Klammern erlaubt sind, war es nicht möglich, das Argument zu klammern. Der C Preprocessor wertet jedoch jedes Komma, das nicht geklammert ist, als Trennung zwischen zwei Argumenten. Ich benötigte also eine Lösung für die Verarbeitung eines nicht klammerbaren Arguments in einem Makro.

Im folgenden stelle ich euch erst das Problem anhand eines Beispiels vor, bevor ich euch im Anschluss erkläre, wie ihr mit Kommata in nicht klammerbaren Argumenten von Makros umgehen könnt.

Direkt zur Lösung springen.

Ein Beispiel

Das eigentliche Problem erkläre ich Euch an einem kleinen Beispiel. Zugegeben: das Beispiel ist nicht gerade sehr praxisnah. Darum geht es aber auch nicht. Es zeigt auf einfache Weise das Problem und dessen Lösung. Der Fokus liegt auf der Einfachheit und der Konzentration auf das eigentliche Problem. Damit seid Ihr dann in der Lage, das ganze auch auf komplexere Problemstellungen zu übertragen.

Ausgangspunkt ist ein Makro zum Deklarieren einer statischen Variable:

#define DECLARE_STATIC(type, name, init) static type name = init

Wie gesagt, nicht besonders schön aber anschaulich.

Verwendet man das Makro mit einem primitiven Typ ist alles gut. Der Ausdruck

DECLARE_STATIC(int, foo, 0);

wird vom C Preprocessor in den Ausdruck

static int  foo = 0;

übersetzt.

Wendet man das Makro jedoch auf einen komplexen Typ an, wird man vom Compiler mit einem Fehler belohnt:

typedef struct {
    int x;
    int y;
} point_t;

DECLARE_STATIC(point_t, bar, {0, 1});
error: macro "DECLARE_STATIC" passed 4 arguments, but takes just 3

Der Grund dafür ist, dass der C Preprocessor das Komma als Trennung zum nächsten Argument erkennt. Nur als geklammerter Ausdruck bleibt das Komma als Bestandteil des Arguments erhalten:

DECLARE_STATIC(point_t, bar, ({0, 1}));

Aber auch das wird mit einem Fehler quittiert, denn Klammern um einen Initializer sind in C nicht erlaubt:

error: braced-group within expression allowed only inside a function

Die Lösung

Die erste Lösung ist ein zusätzliches Makro mit einer variablen Argumentenliste:

#define UNWRAP(...) __VA_ARGS__
#define DECLARE_STATIC(type, name, init) static type name = UNWRAP init

DECLARE_STATIC(int, foo, (0));
DECLARE_STATIC(point_t, bar, ({0, 1}));

Diese Lösung ist etwas allgemeingültiger, da es auf jedes beliebige Argument angewendet werden kann, unabhängig seiner Position. Es erfordert jedoch das zusätzliche Makro und das Argument muss auf jeden Fall beim Aufruf geklammert werden.

Die Funktionsweise ist eigentlich recht simpel. Bei dem Makro UNWRAP handelt es sich um ein sogenanntes variadic Macro. Dieses nimmt eine beliebige Anzahl an Argumenten an, zu erkennen an den drei Auslassungspunkten („ellipsis„). Über __VA_ARGS__ werden alle Argumente wieder ausgegeben. Durch die Klammerung beim Aufruf, bleibt init als einzelnes Argument erhalten. Dieses wird anschließend dem Makro UNWRAP übergeben. Da es bereits geklammert ist, erfolgt der Aufruf ohne Klammern! UNWRAP wertet die durch Komma getrennten Elemente als separate Argumente und gibt diese wieder aus. Es entfernt also quasi wieder die Klammerung.

Das vom C Preprocessor gelieferte Ergebnis sieht dann wie folgt aus:

static int  foo = 0;
static point_t  bar = {0, 1};

Im konkreten Fall gibt es auch noch eine zweite, einfachere Lösung. Diese funktioniert nur, wenn das Argument mit den Kommata am Ende steht:

#define DECLARE_STATIC(type, name, ...) static type name = __VA_ARGS__

DECLARE_STATIC(point_t, foo, 0);
DECLARE_STATIC(point_t, bar, {0, 1});

Durch die direkte Verwendung als variadic Macro entfällt die zusätzliche Klammerung und das Hilfsmakro. Das Ergebnis ist absolut identisch mit der ersten Lösung.

Weitere Informationen zum Thema variadic Macro findet Ihr auf der gcc Dokumentation:
https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

Argument C Makro Preprocessor Variadic
Avatar-Foto
Geschrieben von:

TomTitan

Alle Beiträge anzeigen

Themen

  • Cookbook
  • Helpdesk

Neueste Beiträge

  • Kommata in nicht klammerbaren Argumenten von Makros
  • Abstürtze bei AMD Grafikkarten und Ultra LOW POWER STATE

Archiv

  • Januar 2022

Website

  • Impressum
  • Datenschutzerklärung
  • Kontakt

Social Media

GithubStackoverflowMail

Meta

  • Anmelden
  • Feed der Einträge
  • Kommentar-Feed
  • WordPress.org

Stolz präsentiert von WordPress | Theme: BusiCare Dark von SpiceThemes