C Preprozessor-Trickserei

OP #7366276
Lesenswert?

Mit dem C Preprozessor kann man Symbole generieren, in deren Namen die Zeilennummer steckt, an der sie erzeugt wurden:

1
#define CAT(x, y)  x ## y
2
#define CAT2(x, y) CAT(x, y)
3
#define VARIABLE   CAT2(variable, __LINE__)
4

5
int VARIABLE;      // erzeugt variable5

So weit so schön. Aber kann man das noch weiter treiben? Ich hätte gerne ein Macro, das in einem Header ganz oben im Quellfile definiert ist und in der Expansion ein Symbol einsetzt, das zur Definitionszeit des Macros noch gar nicht existiert:

1
// Header
2
#define SPEZIAL()    spezial(<MODULE_ID>, <__LINE__>)
3

4
// Ende des Headers
5

6
#define MODULE_ID    13
7

8
SPEZIAL()            // soll expandieren zu spezial(13, 8)

Geht sowas überhaupt und wenn ja, wie bekommt man es hin?

#7366346
Lesenswert?

Vielleicht kannst du statt einer Modul ID einfach das static keyword nehmen? Und schau dir eventuell auch mal __attribute__((constructor)) an. Für innerhalb von Funktionen, schau dir scope blöcke und compound literale an. { int x; }, (int){123}.

Wenn man die geschickt einsetzt, braucht man meistens gar keine Hacks mit solchen magischen Modulnummern mehr, und oft kann man auch ohne __LINE__ auskommen.

OP #7366574
Lesenswert?

Daniel A. schrieb:

Vielleicht kannst du statt einer Modul ID einfach das static keyword nehmen?

Ich habe nicht geschrieben, wofür ich das brauche… Das hole ich jetzt nach:

Mein Programmpaket enthält einen Header namens config.h, der in alle Module an erster Stelle nach den Library-Includes includet wird. Dort stehen die grundlegenden Generierungsparameter drin und unter anderem auch die Modul-IDs für jeden .c-File und die Fehlernummern, die in dem jeweiligen Modul vorkommen. (Die ModulID und Fehlernummer kann bei fatalen Fehlern mit einer LED signalisiert werden und anhand der Nummern findet man mittels config.h sehr schnell die Stelle, an der es gerumst hat.)

In jedem .c-Modul steht nach den includes ein #define MODULE_ID MODULE_MAIN_C oder wie der Modul auch immer heißt. (In config.h ist MODULE_MAIN_C als Zahlwert definiert.)

So weit das Vorgeplänkel, das den Trick mit dem cpp lohnend macht. Nun zum aktuellen Problem: ich will den Watchdog so integrieren, dass er dem Programm mit einem Reset wieder auf die Beine hilft, wenn es sich irgenwo verlaufen hat.

Dummerweise hatte ich in etlichen Modulen und teilweise ganz tief in den Hardware-Dingen wdt_rest() Aufrufe eingebaut. Als ich nun die wdt_reset an kritischen Stellen im Programm testweise lahmlegte, hätte der WD einen Reset machen sollen – hat er aber nicht, weil eben irgendwo noch andere wdt_reset-Aufrufe waren…

Was tun?

Ich habe in die config.h folgendes Fragment eingefügt:

1
    #ifndef ALLOW_WDT_ACCESS
2
        #undef wdt_reset
3
        void __wdt_reset(uint8_t moduleID, uint16_t line);
4

5
        #define WDT_CAT(x, y)       x, y
6
        #define WDT_CAT2(x, y)      WDT_CAT(x, y)
7

8
        #define wdt_reset()         __wdt_reset(WDT_CAT2(MODULE_ID, __LINE__))
9
    #endif // ALLOW_WDT_ACCESS

Nur in watchdog.c ist ALLOW_WDT_ACCESS definiert – damit kann nur der ein echtes wdt_reset ausführen.

Alle andern Module expandieren statt dem üblichen wdt_reset aus avr/wdt.h zu

1
__wdt_reset(MODULE_ID, __LINE__)

Die Funktion, die statt des Macros aufgerufen wird sieht so aus:

1
void __wdt_reset(uint8_t moduleID, uint16_t line) {
2
    uart_puts_P(PSTR("wdt_reset: "));
3
    uintOut(moduleID);
4
    uart_putchar(',');
5
    uintOut(line);
6
    nl();
7

8
    wdt_reset();
9
}

Weil sie in watchdog.c liegt, wird dort wdt_reset aus avr/wdt.h ausgeführt.

Damit ist mit geringsten Eingriffen eine Trace-Funktion für wdt_reset implementiert, die sich natürlich mit noch weniger Handgriffen wieder deaktivieren lässt…

OP #7366628
Lesenswert?

Ja auch. Überleg dir einfach mal, wie oft der Text in meinem Code vorkommt und wie oft func bei einer an vielen Stellen aufgerufenen Funktion im Maschinencode vorkäme.

Auf "richtigen" Rechnern kann man sowas machen, auf einem 328, der nicht mehr so viel Platz im Flash hat, eher nicht.

#7366660
Lesenswert?

Moriz schrieb:

Damit ist mit geringsten Eingriffen eine Trace-Funktion für wdt_reset implementiert,

Wenn ich das richtig sehe, brauchst Du keine Makroexpansion dazwischen.

Wenn Du eine echte Funktion aufrufst, werden Makros von allein ersetzt.

1
 #define wdt_reset() __wdt_reset(MODULE_ID, __LINE__)

sollte reichen. CAT2 & Co brauchst Du nur für Function-Like-Makros mit Parametern.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren