web-dev-qa-db-fra.com

Comment écrire une boucle while avec le préprocesseur C?

Je pose cette question d'un point de vue éducatif/piratage (je ne voudrais pas vraiment coder comme ça).

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

66
Tarski

Jetez un œil à la bibliothèque Boost preprocessor , qui vous permet d'écrire des boucles dans le préprocesseur, et bien plus encore.

9
CesarB

Si vous souhaitez implémenter une boucle while, vous devrez utiliser la récursivité dans le préprocesseur. La façon la plus simple de faire la récursivité est d'utiliser une expression différée. Une expression différée est une expression qui nécessite plus d'analyses pour se développer complètement:

#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 désactivant. Ce contexte de désactivation entraînera un jeton, qui fait référence à la macro en cours d'expansion, à être peint en bleu. Ainsi, une fois son bleu peint, la macro ne se dilate plus. C'est pourquoi les macros ne se développent pas récursivement. Cependant, un contexte de désactivation n'existe que pendant une analyse, donc en différant une expansion, nous pouvons empêcher nos macros de devenir peintes en bleu. Nous aurons juste besoin d'appliquer plus d'analyses à 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 faire de la logique (comme 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 récursive WHILE. Nous utilisons un WHILE_INDIRECT macro pour faire référence à elle-même de manière récursive. Cela empêche la macro d'être peinte en bleu, car elle se développera 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 d'opérateur à l'état jusqu'à ce que la macro de prédicat renvoie false (qui est 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 lorsque 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 simplement 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__)

Puis en utilisant la macro WHILE:

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

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

102
Paul Fultz II

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 manière similaire. Découvrez ( Programmation générative

10
Andru Luvisi

J'utilise la programmation de méta-modèles à cet effet, c'est amusant une fois que vous vous y êtes familiarisé. Et très utile parfois lorsqu'il est utilisé avec discrétion. Parce que, comme mentionné, sa turing est terminée, au point où vous pouvez même amener le compilateur à entrer dans une boucle infinie, ou à débordement de pile! Rien de tel que d'aller chercher du café juste pour découvrir que votre compilation utilise plus de 30 gigaoctets de mémoire et tout le CPU pour compiler votre code de boucle infinie!

5
Robert Gould

Voici un abus des règles qui permettraient de le faire légalement. Écrivez votre propre préprocesseur C. Faites-lui interpréter certaines directives #pragma comme vous le souhaitez.

5
Windows programmer

bon, pas que ce soit une boucle while, mais une boucle counter, néanmoins la boucle est possible en clean CPP (pas de templates et pas 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]);
}
5
Vlad

J'ai trouvé ce schéma utile lorsque le compilateur est devenu grincheux et ne déroulait 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 devez é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 par une seule commande. Je l'ai fait.

2
Mikael

Pas tout à fait ce que vous avez demandé, mais vérifiez ces liens vers un programme C qui est également un makefile et un script Shell valides.

Le code C, make et Shell s'appuient les uns sur les autres pour créer un programme C (?) Qui, lorsqu'il est exécuté en tant que script Shell, se compilera via le compilateur C à l'aide d'un makefile!

Un gagnant du concours C obscurci 2000.

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

1
Kevin Beck