Question

Je pose cette question d'un point de vue pédagogique / informatique, (je ne voudrais pas vraiment coder de la sorte).

Est-il possible d'implémenter une boucle while uniquement à l'aide des directives du préprocesseur C ? Je comprends que les macros ne peuvent pas être développées de manière récursive, alors comment cela serait-il accompli?

Était-ce utile?

La solution

Consultez le accélération du préprocesseur bibliothèque, qui vous permet d’écrire des boucles dans le préprocesseur et bien plus encore.

Autres conseils

Si vous souhaitez implémenter une boucle while, vous devez utiliser la récursion dans le préprocesseur. Le moyen le plus simple de faire de la récursivité consiste à utiliser une expression différée. Une expression différée est une expression qui nécessite davantage d’analyses pour développer pleinement:

#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

Pourquoi est-ce important? Eh bien, lorsqu'une macro est analysée et développée, elle crée un contexte invalidant. Ce contexte de désactivation fera apparaître un jeton, faisant référence à la macro en expansion, peint en bleu. Ainsi, une fois peinte en bleu, la macro ne se développera plus. C'est pourquoi les macros ne se développent pas de manière récursive. Toutefois, un contexte de désactivation n'existe que pendant une analyse. Par conséquent, en différant une extension, nous pouvons empêcher nos macros de devenir peintes en bleu. Nous aurons juste besoin d'appliquer plus de balayages à l'expression. Nous pouvons le faire en utilisant cette macro EVAL :

#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__

Ensuite, nous définissons certains opérateurs pour effectuer certaines opérations logiques (telles que if, etc.):

#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))

Maintenant, avec toutes ces macros, nous pouvons écrire une macro WHILE récursive. Nous utilisons une macro WHILE_INDIRECT pour faire référence à elle-même de manière récursive. Cela empêche que la macro soit peinte en bleu, car elle se développerait sur une analyse différente (et en utilisant un contexte de désactivation différent). La macro WHILE prend une macro de prédicat, une macro d'opérateur et un état (qui sont les arguments variadiques). Il continue d’appliquer cette macro opérateur à l’état jusqu’à ce que la macro de prédicat renvoie la valeur false (égale à 0).

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

À des fins de démonstration, nous allons simplement créer un prédicat qui vérifie que le nombre d'arguments est égal à 1:

#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__)))

Ensuite, nous créons un opérateur, que nous allons concaténer deux jetons. Nous créons également un opérateur final (appelé M ) qui traitera la sortie finale:

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

Ensuite, en utilisant la macro WHILE :

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

Bien sûr, tout type de prédicat ou d'opérateur peut lui être transmis.

Vous utilisez des fichiers d'inclusion récursifs. Malheureusement, vous ne pouvez pas itérer la boucle plus que la profondeur maximale autorisée par le préprocesseur.

Il s’avère que les modèles C ++ sont Turing Complete et peuvent être utilisés de la même manière. Découvrez la Programmation générative

.

Voici un abus des règles qui permettrait de le faire en toute légalité. Écrivez votre propre préprocesseur. Faites-le interpréter certaines directives #pragma comme vous le souhaitez.

J’utilise la programmation par méta-modèles à cette fin, c’est amusant une fois que vous l’avez compris. Et très utile parfois lorsqu'il est utilisé avec discrétion. Parce que, comme mentionné, sa complétude complète, au point où vous pouvez même amener le compilateur à entrer dans une boucle infinie, ou pile-débordement! Rien de tel que d'aller prendre un café rien que pour constater que votre compilation utilise plus de 30 gigaoctets de mémoire et tout le processeur nécessaire pour compiler votre code de boucle infinie!

eh bien, ce n’est pas une boucle while, mais une boucle de compteur, mais la boucle est possible dans un CPP propre (pas de modèles ni de 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]);
}

J'ai trouvé ce schéma utile lorsque le compilateur est devenu grincheux et que je ne déroulerais pas certaines boucles pour moi

  

#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));

Mais à mon humble avis, si vous avez besoin de quelque chose de beaucoup plus compliqué que cela, alors vous devriez écrire votre propre pré-pré-processeur. Votre pré-préprocesseur pourrait par exemple générer un fichier d’en-tête approprié pour vous, et il est assez facile d’inclure cette étape dans un Makefile pour que tout soit compilé en douceur à l’aide d’une seule commande. Je l'ai fait.

Ce n'est pas tout à fait ce que vous avez demandé, mais vérifiez ces liens vers un programme C qui est également un script makefile et shell valide.

  

Le code C, la marque et le code shell reposent sur   mutuellement pour créer un programme C (?)   qui, lorsqu'il est exécuté en tant que script shell   se compilera à travers le C   compilateur utilisant un makefile!

Un gagnant du concours C 2000 dissimulé.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top