C Makros

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Hier entsteht eine Sammlung verschiedener, für die hardwarenahe Programmierung nützlicher, C-Makros.

Bitmanipulation

/* Bit setzen */
#define set_bit(var, bit) ((var) |= (1 << (bit)))

/* Bit löschen */
#define clear_bit(var, bit) ((var) &= (unsigned)~(1 << (bit)))

/* Bit togglen */
#define toggle_bit(var,bit) ((var) ^= (1 << (bit)))

/* Bit abfragen */
#define bit_is_set(var, bit) ((var) & (1 << (bit)))
#define bit_is_clear(var, bit) !bit_is_set(var, bit)

/* Konstante in Binärschreibweise angeben (max. 8 Bit) */
/* Beispiel: BIN(1001011)                              */
/*               ^------ ACHTUNG, stets OHNE fuehrende */
/*                       0 angeben, da sonst Oktalzahl */
#define BIN(x) \
        (((x)%10)\
         |((((x)/10)%10)<<1)\
         |((((x)/100)%10)<<2)\
         |((((x)/1000)%10)<<3)\
         |((((x)/10000)%10)<<4)\
         |((((x)/100000)%10)<<5)\
         |((((x)/1000000)%10)<<6)\
         |((((x)/10000000)%10)<<7)\
         )

/* Alternative mit Kommatrennung (genau 8 Bit angeben) */
/* Beispiel: BIN8(0,1,0,0,1,0,1,1)                     */
#define BIN8(b7,b6,b5,b4,b3,b2,b1,b0) \
        ((b0)\
         |((b1)<<1)\
         |((b2)<<2)\
         |((b3)<<3)\
         |((b4)<<4)\
         |((b5)<<5)\
         |((b6)<<6)\
         |((b7)<<7)\
        )

/*Reihenfolge der Bits vertauschen (7..0 -> 0..7)
/*z.&nbsp;B. 10110010 -> 01001101
/*inline assembler für AVR, da die C-Variante mehr als 4 Mal größer und 6 Mal langsamer ist. */
#define swapbits(x) asm ( \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	"lsl %1\n\t ror %0 \n\t " \
	: "=&r" (x): "r" (x))
/* am Ende hat das Macro kein Semikolon, das das bei jedem 
   Aufruf "swapbits(x);" angegeben wird. */

Sonstiges

/* 2 Variablen ohne Zwischenspeicher vertauschen */
#define SWAP(x, y)  { (x) ^= (y); (y) ^= (x); (x) ^= (y);}

Dies funktioniert bei Ganzzahlen und Pointern gleicher Größe (gemäß sizeof()) und Endianess sowie Representation (was meist der Fall ist). Eine weitere (harmlose) Voraussetzung ist, dass die beiden nicht überlappen.

Darüber hinaus sind aber ganz allgemein Makros, die eines ihrer Argumente mehrfach im Ersatztext verwenden, mit einer gewissen Vorsicht zu benutzen. So wird sich beispielsweise der folgende Code nicht gemäß den Erwartungen des (flüchtigen) Lesers verhalten, der sich SWAP nicht zuvor genau angesehen hat:

        SWAP(*(p++), z);

Der Zeiger "p" wird hier nämlich gleich DREIMAL inkrementiert!

Schliesslich sind Makros, die wie der obige einen Block, in geschweiften Klammern enthalten sind, auch für manche "syntaktische" Überraschung gut:

        if (a < b)
                SWAP(a, b); /* <--- so geht's nicht */
        else
                ...

Das Semikolon nach SWAP, das bei "normalen" Funktionsaufrufen eigentlich stehen müsste, löst oben einen Syntaxfehler aus! Überraschender Weise funktioniert es aber auch mit Semikolon, so lange es keinen else-Zweig gibt.
Wenn man drüber nachdenkt, dann ist das aber nicht wirklich verwunderslich, denn es entsteht ja durch den Preprozossor folgendes:

        if (a < b)
                { (a) ^= (b); (b) ^= (a); (a) ^= (b);};
        ...

was etwas anders geschrieben so aussieht

        if (a < b)
                { (a) ^= (b); (b) ^= (a); (a) ^= (b);}
        ; /* <---- das Semikolon, eine leere Anweisung */
        ...

Jetzt wäre natürlich eine Lösung schön, wie das Problem ein für alle Mal aus der Welt schafft. Und die gibt es mit der folgenden Konstruktion tatsächlich:

/* 2 Variablen ohne Zwischenspeicher vertauschen */
#define SWAP(x, y)  do { (x) ^= (y); (y) ^= (x); (x) ^= (y);} while(FALSE)

Neu ist der Block

do {...} while(FALSE)

Dieser macht aber praktisch nichts, aber er wird genau einmal durchlaufen (und der Compiler sollte so schlau sein, genau das auch zu erkennen). Da aber am Ende das Semikolon fehlt, muss man es bei jedem Makro-Aufruf hinzufügen - sonst nörgelt der Compiler, dass ein Semikolon fehlt . Und damit verhält sich das Macro dann wie eine normale Funktion, so dass es keine Probleme mehr gibt bei:

        if (a < b)
                SWAP(a, b);
        else
                ...

Aber es es schadet auch nicht, sich generell anzugewöhnen, immer der {}-Klammern zu verwenden. Dann vermeidet man das sog. "Dangling-Else-Problem" ein für alle mal.

        if (a < b) {
                SWAP(a, b);
        } else {
                ...
        }