Запись цикла while в препроцессоре C

Я задаю этот вопрос с точки зрения образования/взлома (я бы не хотел так писать код).

Возможно ли реализовать цикл while только с помощью инструкций препроцессора C. Я понимаю, что макросы не могут быть расширены рекурсивно, так как это можно сделать?

Ответ 1

Посмотрите на библиотеку Boost preprocessor, которая позволяет писать циклы в препроцессоре и многое другое.

Ответ 2

Если вы хотите реализовать цикл while, вам нужно будет использовать рекурсию в препроцессоре. Самый простой способ сделать рекурсию - использовать отложенное выражение. Отложенное выражение является выражением, которое требует больше сканирования для полного расширения:

#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

Почему это важно? Хорошо, когда макрос сканируется и расширяется, он создает контекст отключения. Этот контекст отключения приведет к тому, что токен, относящийся к текущему расширяющемуся макросу, будет окрашен в синий цвет. Таким образом, после окрашивания синего цвета макрос больше не будет расширяться. Вот почему макросы не рекурсивно расширяются. Тем не менее, отключенный контекст существует только во время одного сканирования, поэтому, откладывая расширение, мы можем предотвратить окрашивание наших макросов в синий цвет. Нам просто нужно применить к выражению больше сканирований. Мы можем сделать это с помощью этого макроса 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__

Далее мы определим некоторые операторы для выполнения некоторой логики (например, if и т.д.):

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

Теперь со всеми этими макросами мы можем написать рекурсивный макрос WHILE. Мы используем макрос WHILE_INDIRECT, чтобы возвращать себе рекурсивно. Это предотвращает окрашивание макроса в синий цвет, поскольку оно будет расширяться при другом сканировании (и использовании другого контекста отключения). Макрос WHILE принимает предикат-макрос, макрос оператора и состояние (которое является вариационным аргументом). Он продолжает применять этот макрос оператора к состоянию до тех пор, пока предикатный макрос не вернет false (что равно 0).

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

Для демонстрационных целей мы просто создадим предикат, который проверяет, когда количество аргументов равно 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__)))

Затем мы создаем оператор, который мы просто выполним для двух токенов. Мы также создаем конечный оператор (называемый M), который будет обрабатывать конечный результат:

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

Затем с помощью макроса WHILE:

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

Конечно, любой тип предиката или оператора может быть передан ему.

Ответ 3

Вы используете рекурсивные включенные файлы. К сожалению, вы не можете перебирать цикл больше, чем максимальная глубина, которую позволяет препроцессор.

Оказывается, что С++-шаблоны являются Turing Complete и могут использоваться аналогичным образом. Проверьте Generative Programming

Ответ 4

Здесь злоупотребление правилами, которые позволят сделать это законно. Напишите свой собственный препроцессор C. Попробуйте интерпретировать некоторые директивы #pragma так, как вы хотите.

Ответ 5

Я использую мета-шаблонное программирование для этой цели, его удовольствие, когда вы его повесите. И очень полезно иногда, когда используется с усмотрением. Поскольку, как упоминалось, его turing завершен, до такой степени, что вы даже можете заставить компилятор попасть в бесконечный цикл или переполнение стека! Нет ничего лучше, чем выпить кофе, чтобы найти компиляцию, используя 30+ гигабайт памяти и весь процессор для компиляции вашего бесконечного кода цикла.

Ответ 6

ну, не то, что это цикл while, но контурный цикл, тем не менее цикл возможен в чистом CPP (без шаблонов и без С++)

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

Ответ 7

Я нашел эту схему полезной, когда компилятор запутался и не разблокировал определенные циклы для меня

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

Но ИМХО, если вам нужно что-то гораздо более сложное, тогда вы должны написать свой собственный пре-препроцессор. Ваш пре-препроцессор мог бы, например, создать для вас соответствующий заголовочный файл, и достаточно легко включить этот шаг в Makefile, чтобы все было скомпилировано одной командой. Я сделал это.

Ответ 8

Не совсем то, что вы просили, но проверьте эти ссылки на C-программу, которая также является действительным make файлом и оболочкой script.

Код C, make и shell основывается на друг друга, чтобы создать программу на C (?) который при выполнении в виде оболочки scriptскомпилируется через C компилятор с использованием make файла!

Победитель в запутанном конкурсе C. в 2000 году.

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