Как написать цикл while с препроцессором C? - PullRequest
66 голосов
/ 26 ноября 2008

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

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

Ответы [ 8 ]

100 голосов
/ 11 мая 2012

Если вы хотите реализовать цикл 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

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

10 голосов
/ 26 ноября 2008

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

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

9 голосов
/ 26 ноября 2008

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

6 голосов
/ 26 ноября 2008

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

5 голосов
/ 29 декабря 2011

ну, это не то, что это цикл while, а цикл счетчика, тем не менее цикл возможен в чистом CPP (без шаблонов и без 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 голосов
/ 26 ноября 2008

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

2 голосов
/ 01 февраля 2011

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

# определить REPEAT20 (х) {х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; х; }

REPEAT20 (val = пожалуйста, сходятся (val));

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

1 голос
/ 26 ноября 2008

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

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

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

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

...