73 votes

Ecriture d'une boucle while dans le préprocesseur C

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

Est-il possible d'implémenter une boucle while uniquement en utilisant les 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?

Merci

139voto

Paul Points 4552

Si vous souhaitez mettre en place une boucle while, vous aurez besoin d'utiliser la récursivité dans le préprocesseur. La meilleure façon de faire de la récursivité est d'utiliser un différé d'expression. Un différé d'expression est une expression qui nécessite des analyses plus expansion:

#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? Ainsi lorsqu'une macro est scanné et se développe, il crée une désactivation de contexte. Cette désactivation est le contexte qui va provoquer un jeton, qui fait référence à l'expansion de la macro, pour être peinte en bleu. Ainsi, une fois peint en bleu, la macro va plus s'étendre. C'est pourquoi les macros ne développent pas de manière récursive. Cependant, la désactivation de contexte n'existe que pendant une analyse, par le report d'un agrandissement, nous pouvons empêcher nos macros de devenir peint en bleu. Nous allons juste besoin d'appliquer des analyses plus à l'expression. Nous pouvons le faire à l'aide de cette EVAL macro:

#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 un peu de logique(comme si, 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 tous ces macros, nous pouvons écrire un récursif WHILE macro. Nous utilisons un WHILE_INDIRECT macro pour renvoyer à lui-même de manière récursive. Cela empêche la macro d'être peint en bleu, car il s'étendra sur un autre scan(et en utilisant un autre désactivation de contexte). L' WHILE macro prend un prédicat macro, un opérateur de macro, et un état(ce qui est le variadic arguments). Il maintient l'application de cet opérateur de macro à l'état jusqu'à ce que le prédicat de la macro renvoie la valeur false(0).

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

Pour les besoins de la démonstration, nous allons créer un prédicat qui vérifie si le nombre d'arguments sont de 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, ce qui, nous allons juste concat deux jetons. Nous avons également créer un opérateur final(appelé M) qui traitera le résultat final:

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

Ensuite, à l'aide de l' WHILE macro:

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

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

10voto

CesarB Points 18048

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

9voto

Glomek Points 12183

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

6voto

Robert Gould Points 29406

J'utilise la programmation de méta-modèles à cet effet, c'est amusant une fois que vous 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 trouver que votre compilation utilise plus de 30 gigaoctets de mémoire et tout le processeur nécessaire pour compiler votre code de boucle infinie!

5voto

Windows programmer Points 5365

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X