it-swarm.com.de

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

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

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

66
Tarski

Schauen Sie sich die Bibliothek Boost-Präprozessor an, mit der Sie Schleifen in den Präprozessor schreiben können, und vieles mehr.

9
CesarB

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

#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? Wenn ein Makro gescannt und erweitert wird, wird ein deaktivierender Kontext erstellt. Dieser deaktivierende Kontext bewirkt, dass ein Token, das auf das aktuell expandierende Makro verweist, blau dargestellt wird. Sobald das Makro blau dargestellt ist, wird es nicht mehr erweitert. Aus diesem Grund werden Makros nicht rekursiv erweitert. Ein deaktivierender Kontext ist jedoch nur während eines Scans vorhanden. Wenn Sie also eine Erweiterung verschieben, können Sie verhindern, dass die Makros blau dargestellt werden. Wir müssen nur mehr Scans auf den Ausdruck anwenden. Wir können das mit diesem EVAL Makro machen:

#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 für die Ausführung einer Logik (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))

Mit all diesen Makros können wir nun ein rekursives WHILE Makro schreiben. Wir benutzen ein WHILE_INDIRECT Makro, um rekursiv auf sich selbst zu verweisen. Dadurch wird verhindert, dass das Makro blau dargestellt wird, da es bei einem anderen Scan erweitert wird (und einen anderen Deaktivierungskontext verwendet). Das Makro WHILE enthält ein Prädikatmakro, ein Operatormakro und einen Status (das sind die variadischen Argumente). Es wendet dieses Operatormakro so lange auf den Status an, bis das Prädikatmakro false (0) 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, der nur zwei Token zusammenfasst. Wir erstellen auch einen finalen Operator (genannt M), der die endgültige Ausgabe verarbeitet:

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

Verwenden Sie dann das Makro WHILE:

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

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

102
Paul Fultz II

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

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

10
Andru Luvisi

Zu diesem Zweck verwende ich die Meta-Template-Programmierung, die Spaß macht, sobald Sie sich ein Bild davon gemacht haben. Und manchmal sehr nützlich, wenn man es mit Diskretion anwendet. Weil, wie bereits erwähnt, die Funktion vollständig ist und Sie sogar den Compiler in eine Endlosschleife oder einen Stapelüberlauf versetzen können! Es gibt nichts Schöneres als einen Kaffee zu trinken, nur um herauszufinden, dass Ihre Kompilierung über 30 Gigabyte Speicherplatz und die gesamte CPU zum Kompilieren Ihres Endlosschleifencodes benötigt!

5
Robert Gould

Hier ist ein Missbrauch der Regeln, die es legal erledigen würden. Schreiben Sie Ihren eigenen C-Präprozessor. Lassen Sie es einige # Pragma-Direktiven so interpretieren, wie Sie es möchten.

5

nun, nicht, dass es sich um eine while-Schleife handelt, sondern um eine Counter-Schleife. Dennoch 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]);
}
5
Vlad

Ich fand dieses Schema nützlich, als der Compiler launisch wurde und bestimmte Schleifen für mich nicht mehr abwickelte

#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-Pre-Prozessor schreiben. Ihr Pre-Pre-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 werden kann. Ich habe es getan.

2
Mikael

Nicht ganz das, wonach Sie gefragt haben, aber sehen Sie sich diese Links zu einem C-Programm an, das auch ein gültiges Makefile und Shell-Skript ist.

Der C-, make- und Shell-Code bauen aufeinander auf, um ein C-Programm (?) Zu erstellen, das sich bei Ausführung als Shell-Skript mithilfe eines Makefiles über den C-Compiler kompiliert!

Ein Gewinner des 2000 verschleierten C-Wettbewerbs.

http://www.ioccc.org/2000/tomx.c
http://www.ioccc.org/2000/tomx.hint

1
Kevin Beck