Wie schreibe ich eine While-Schleife mit dem C-Präprozessor?

Lesezeit: 10 Minuten

Benutzeravatar von Tarski
Tarski

Ich stelle diese Frage aus pädagogischer/Hacking-Sicht (ich würde nicht wirklich so programmieren wollen).

Ist es möglich, eine While-Schleife nur mit zu implementieren? C Präprozessor-Direktiven. Ich verstehe, dass Makros nicht rekursiv erweitert werden können, also wie würde dies erreicht werden?

Benutzeravatar von Paul Fultz II
Paul FultzII

Wenn Sie eine While-Schleife implementieren möchten, müssen Sie die Rekursion im Präprozessor verwenden. Der einfachste Weg, eine Rekursion durchzuführen, ist die Verwendung eines zurückgestellten Ausdrucks. Ein verzögerter Ausdruck ist ein Ausdruck, der mehr Scans erfordert, um vollständig erweitert zu werden:

#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(id) id DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__

#define A() 123
A() // Expands to 123
DEFER(A)() // Expands to A () because it requires one more scan to fully expand
EXPAND(DEFER(A)()) // Expands to 123, because the EXPAND macro forces another scan

Warum ist das wichtig? Nun, wenn ein Makro gescannt und erweitert wird, erstellt es einen deaktivierenden Kontext. Dieser deaktivierende Kontext bewirkt, dass ein Token, das sich auf das aktuell expandierende Makro bezieht, blau gefärbt wird. Sobald es also blau gefärbt ist, wird das Makro nicht mehr erweitert. Aus diesem Grund werden Makros nicht rekursiv erweitert. Ein deaktivierender Kontext existiert jedoch nur während eines Scans, sodass wir durch Zurückstellen einer Erweiterung verhindern können, dass unsere Makros blau gefärbt werden. Wir müssen nur mehr Scans auf den Ausdruck anwenden. Das können wir damit machen EVAL Makro:

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__

Als Nächstes definieren wir einige Operatoren, um eine Logik auszuführen (z. B. if usw.):

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

Jetzt können wir mit all diesen Makros eine rekursive schreiben WHILE Makro. Wir benutzen ein WHILE_INDIRECT Makro rekursiv auf sich selbst zurückgreifen. Dadurch wird verhindert, dass das Makro blau gezeichnet wird, da es bei einem anderen Scan erweitert wird (und einen anderen Deaktivierungskontext verwendet). Das WHILE Makro nimmt ein Prädikat-Makro, ein Operator-Makro und einen Zustand (das sind die variadischen Argumente). Es wendet dieses Operator-Makro so lange auf den Zustand an, bis das Prädikat-Makro „false“ (was 0 ist) zurückgibt.

#define WHILE(pred, op, ...) \
    IF(pred(__VA_ARGS__)) \
    ( \
        OBSTRUCT(WHILE_INDIRECT) () \
        ( \
            pred, op, op(__VA_ARGS__) \
        ), \
        __VA_ARGS__ \
    )
#define WHILE_INDIRECT() WHILE

Zu Demonstrationszwecken erstellen wir nur ein Prädikat, das prüft, ob die Anzahl der Argumente 1 ist:

#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

#define IS_1(x) CHECK(PRIMITIVE_CAT(IS_1_, x))
#define IS_1_1 ~, 1,

#define PRED(x, ...) COMPL(IS_1(NARGS(__VA_ARGS__)))

Als nächstes erstellen wir einen Operator, mit dem wir einfach zwei Token verketten. Wir erstellen auch einen letzten Operator (genannt M), die die endgültige Ausgabe verarbeiten:

#define OP(x, y, ...) CAT(x, y), __VA_ARGS__ 
#define M(...) CAT(__VA_ARGS__)

Dann mit der WHILE Makro:

M(EVAL(WHILE(PRED, OP, x, y, z))) //Expands to xyz

Natürlich kann ihm jede Art von Prädikat oder Operator übergeben werden.

  • Könnten Sie die Makros bitte etwas erklären? Es ist wirklich interessant, aber schwer zu bekommen. all diese Querverweise. Ich verstehe die letzte Zeile nicht. Was ist PRED wie es durch das Nehmen definiert wird x und hier: M(EVAL(WHILE(**PRED**, OP, x, y, z))) //Expands to xyz es nimmt nichts

    – dhein

    18. Dezember 2014 um 14:29 Uhr


  • Einige weitere Erklärungen, wie das alles funktioniert, mit weiteren Schritten dazwischen: hier oder hier. Die Beispiele in diesen Artikeln sind nicht genau die gleichen wie in dieser Antwort, aber sie sind nah genug dran.

    – Joris

    16. Juni 2016 um 14:46 Uhr


  • Schöne Antwort – wenn es nur funktionieren würde. Aber statt xyzes erweitert sich zu WHILE ( PRED, OP, xy, z )vgl. coliru.stacked-crooked.com/a/1a7b4e7879bd7d4b

    – Armali

    16. November 2017 um 12:41 Uhr

  • @Armali Hoppla, da war ein Tippfehler. Es sollte sein OBSTRUCT(WHILE_INDIRECT) nicht DEFER(WHILE_INDIRECT). Sehen: coliru.stacked-crooked.com/a/a381ddcbcae0b890

    – Paul FultzII

    16. November 2017 um 22:50 Uhr


Werfen Sie einen Blick auf die Boost-Präprozessor Bibliothek, mit der Sie Schleifen im Präprozessor schreiben können, und vieles mehr.

Benutzeravatar von Andru Luvisi
Andru Luisi

Sie verwenden rekursive Include-Dateien. Leider können Sie die Schleife nicht mehr als die maximale Tiefe durchlaufen, die der Präprozessor zulässt.

Es stellt sich heraus, dass C++-Vorlagen Turing Complete sind und auf ähnliche Weise verwendet werden können. Kasse Generative Programmierung

Ich benutze Meta-Template-Programmierung für diesen Zweck, es macht Spaß, wenn Sie es einmal verstanden haben. Und manchmal sehr nützlich, wenn es mit Diskretion verwendet wird. Denn wie bereits erwähnt, ist der Vorgang abgeschlossen, bis zu dem Punkt, an dem Sie sogar den Compiler dazu bringen können, in eine Endlosschleife oder einen Stapelüberlauf zu geraten! Es gibt nichts Schöneres, als sich einen Kaffee zu holen, nur um festzustellen, dass Ihre Kompilierung mehr als 30 Gigabyte Speicher und die gesamte CPU verbraucht, um Ihren Endlosschleifencode zu kompilieren!

Nun, nicht dass es eine While-Schleife ist, sondern eine Counter-Schleife, trotzdem ist die Schleife in sauberem CPP möglich (keine Templates und kein C++)

#ifdef pad_always

#define pad(p,f) p##0

#else

#define pad0(p,not_used) p
#define pad1(p,not_used) p##0

#define pad(p,f) pad##f(p,)

#endif

// f - padding flag
// p - prefix so far
// a,b,c - digits
// x - action to invoke

#define n0(p,x)
#define n1(p,x)         x(p##1)
#define n2(p,x) n1(p,x) x(p##2)
#define n3(p,x) n2(p,x) x(p##3)
#define n4(p,x) n3(p,x) x(p##4)
#define n5(p,x) n4(p,x) x(p##5)
#define n6(p,x) n5(p,x) x(p##6)
#define n7(p,x) n6(p,x) x(p##7)
#define n8(p,x) n7(p,x) x(p##8)
#define n9(p,x) n8(p,x) x(p##9)

#define n00(f,p,a,x)                       n##a(pad(p,f),x)
#define n10(f,p,a,x) n00(f,p,9,x) x(p##10) n##a(p##1,x)
#define n20(f,p,a,x) n10(f,p,9,x) x(p##20) n##a(p##2,x)
#define n30(f,p,a,x) n20(f,p,9,x) x(p##30) n##a(p##3,x)
#define n40(f,p,a,x) n30(f,p,9,x) x(p##40) n##a(p##4,x)
#define n50(f,p,a,x) n40(f,p,9,x) x(p##50) n##a(p##5,x)
#define n60(f,p,a,x) n50(f,p,9,x) x(p##60) n##a(p##6,x)
#define n70(f,p,a,x) n60(f,p,9,x) x(p##70) n##a(p##7,x)
#define n80(f,p,a,x) n70(f,p,9,x) x(p##80) n##a(p##8,x)
#define n90(f,p,a,x) n80(f,p,9,x) x(p##90) n##a(p##9,x)

#define n000(f,p,a,b,x)                           n##a##0(f,pad(p,f),b,x)
#define n100(f,p,a,b,x) n000(f,p,9,9,x) x(p##100) n##a##0(1,p##1,b,x)
#define n200(f,p,a,b,x) n100(f,p,9,9,x) x(p##200) n##a##0(1,p##2,b,x)
#define n300(f,p,a,b,x) n200(f,p,9,9,x) x(p##300) n##a##0(1,p##3,b,x)
#define n400(f,p,a,b,x) n300(f,p,9,9,x) x(p##400) n##a##0(1,p##4,b,x)
#define n500(f,p,a,b,x) n400(f,p,9,9,x) x(p##500) n##a##0(1,p##5,b,x)
#define n600(f,p,a,b,x) n500(f,p,9,9,x) x(p##600) n##a##0(1,p##6,b,x)
#define n700(f,p,a,b,x) n600(f,p,9,9,x) x(p##700) n##a##0(1,p##7,b,x)
#define n800(f,p,a,b,x) n700(f,p,9,9,x) x(p##800) n##a##0(1,p##8,b,x)
#define n900(f,p,a,b,x) n800(f,p,9,9,x) x(p##900) n##a##0(1,p##9,b,x)

#define n0000(f,p,a,b,c,x)                               n##a##00(f,pad(p,f),b,c,x)
#define n1000(f,p,a,b,c,x) n0000(f,p,9,9,9,x) x(p##1000) n##a##00(1,p##1,b,c,x)
#define n2000(f,p,a,b,c,x) n1000(f,p,9,9,9,x) x(p##2000) n##a##00(1,p##2,b,c,x)
#define n3000(f,p,a,b,c,x) n2000(f,p,9,9,9,x) x(p##3000) n##a##00(1,p##3,b,c,x)
#define n4000(f,p,a,b,c,x) n3000(f,p,9,9,9,x) x(p##4000) n##a##00(1,p##4,b,c,x)
#define n5000(f,p,a,b,c,x) n4000(f,p,9,9,9,x) x(p##5000) n##a##00(1,p##5,b,c,x)
#define n6000(f,p,a,b,c,x) n5000(f,p,9,9,9,x) x(p##6000) n##a##00(1,p##6,b,c,x)
#define n7000(f,p,a,b,c,x) n6000(f,p,9,9,9,x) x(p##7000) n##a##00(1,p##7,b,c,x)
#define n8000(f,p,a,b,c,x) n7000(f,p,9,9,9,x) x(p##8000) n##a##00(1,p##8,b,c,x)
#define n9000(f,p,a,b,c,x) n8000(f,p,9,9,9,x) x(p##9000) n##a##00(1,p##9,b,c,x)

#define n00000(f,p,a,b,c,d,x)                                   n##a##000(f,pad(p,f),b,c,d,x)
#define n10000(f,p,a,b,c,d,x) n00000(f,p,9,9,9,9,x) x(p##10000) n##a##000(1,p##1,b,c,d,x)
#define n20000(f,p,a,b,c,d,x) n10000(f,p,9,9,9,9,x) x(p##20000) n##a##000(1,p##2,b,c,d,x)
#define n30000(f,p,a,b,c,d,x) n20000(f,p,9,9,9,9,x) x(p##30000) n##a##000(1,p##3,b,c,d,x)
#define n40000(f,p,a,b,c,d,x) n30000(f,p,9,9,9,9,x) x(p##40000) n##a##000(1,p##4,b,c,d,x)
#define n50000(f,p,a,b,c,d,x) n40000(f,p,9,9,9,9,x) x(p##50000) n##a##000(1,p##5,b,c,d,x)
#define n60000(f,p,a,b,c,d,x) n50000(f,p,9,9,9,9,x) x(p##60000) n##a##000(1,p##6,b,c,d,x)
#define n70000(f,p,a,b,c,d,x) n60000(f,p,9,9,9,9,x) x(p##70000) n##a##000(1,p##7,b,c,d,x)
#define n80000(f,p,a,b,c,d,x) n70000(f,p,9,9,9,9,x) x(p##80000) n##a##000(1,p##8,b,c,d,x)
#define n90000(f,p,a,b,c,d,x) n80000(f,p,9,9,9,9,x) x(p##90000) n##a##000(1,p##9,b,c,d,x)

#define cycle5(c1,c2,c3,c4,c5,x) n##c1##0000(0,,c2,c3,c4,c5,x)
#define cycle4(c1,c2,c3,c4,x) n##c1##000(0,,c2,c3,c4,x)
#define cycle3(c1,c2,c3,x) n##c1##00(0,,c2,c3,x)
#define cycle2(c1,c2,x) n##c1##0(0,,c2,x)
#define cycle1(c1,x) n##c1(,x)

#define concat(a,b,c) a##b##c

#define ck(arg) a[concat(,arg,-1)]++;
#define SIZEOF(x) (sizeof(x) / sizeof((x)[0]))

void check5(void)
{
    int i, a[32769];

    for (i = 0; i < SIZEOF(a); i++) a[i]=0;

    cycle5(3,2,7,6,9,ck);

    for (i = 0; i < SIZEOF(a); i++) if (a[i] != 1) printf("5: [%d] = %d\n", i+1, a[i]);
}

  • funktioniert das für C oder nur C++?

    – Person der Mensch

    11. Mai um 12:32 Uhr

Benutzeravatar des Windows-Programmierers
Windows-Programmierer

Hier ist ein Missbrauch der Regeln, der es legal machen würde. Schreiben Sie Ihren eigenen C-Präprozessor. Lassen Sie es einige #pragma-Anweisungen so interpretieren, wie Sie es möchten.

  • funktioniert das für C oder nur C++?

    – Person der Mensch

    11. Mai um 12:32 Uhr

Benutzeravatar von Mikael
Michael

Ich fand dieses Schema nützlich, wenn der Compiler launisch wurde und bestimmte Schleifen nicht für mich ausrollen wollte

#define REPEAT20(x) { x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;}

REPEAT20(val = pleaseconverge(val));

Aber IMHO, wenn Sie etwas viel Komplizierteres brauchen, dann sollten Sie Ihren eigenen Pre-Präprozessor schreiben. Ihr Pre-Präprozessor könnte beispielsweise eine entsprechende Header-Datei für Sie generieren, und es ist einfach genug, diesen Schritt in ein Makefile aufzunehmen, damit alles reibungslos mit einem einzigen Befehl kompiliert wird. Ich habe es getan.

1416950cookie-checkWie schreibe ich eine While-Schleife mit dem C-Präprozessor?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy