Как написать цикл while с препроцессором C?
Я задаю этот вопрос с образовательной / хакерской точки зрения (я бы не очень хотел кодировать так).
возможно ли реализовать цикл while только с помощью C директивы препроцессора. Я понимаю, что макросы не могут быть расширены рекурсивно, так как это будет сделано?
8 ответов:
посмотри повышение препроцессора библиотека, которая позволяет писать циклы в препроцессоре, и многое другое.
если вы хотите реализовать цикл 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конечно, любой вид предиката или оператора может быть передан ему.
вы используете рекурсивные файлы включения. К сожалению, вы не можете повторить цикл больше, чем максимальная глубина, которую позволяет препроцессор.
оказывается, что шаблоны C++ являются Turing полными и могут использоваться аналогично. Проверьте Генеративного Программирования
вот злоупотребление правилами, которые сделали бы это законно. Написать собственный препроцессор c. Заставьте его интерпретировать некоторые директивы #pragma так, как вы хотите.
Я использую мета-шаблон программирования для этой цели, его весело как только вы получите повесить его. И очень полезно время от времени, когда используется с осторожностью. Потому что, как уже упоминалось, его Тьюринг завершен, до такой степени, что вы даже можете заставить компилятор попасть в бесконечный цикл или переполнение стека! Нет ничего лучше, чем получить кофе, чтобы найти, что ваша компиляция использует до 30 + гигабайт памяти и весь процессор для компиляции вашего бесконечного кода цикла!
ну, не то, что это цикл 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]); }
Я нашел эту схему полезной, когда компилятор стал капризным и не разворачивал определенные циклы для меня
#определить 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, чтобы все было скомпилировано плавно с помощью одной команды. Я сделал это.
Не совсем то, что вы спросили, но проверьте эти ссылки на программу C, которая также является допустимым скриптом makefile и shell.
C, make и shell код построить на друг друга, чтобы создать программу на C (?) который при выполнении в виде сценария оболочки будет компилировать себя через C компилятор, используя make-файл!
победитель в 2000 запутанном конкурсе C.
http://www.ioccc.org/2000/tomx.c
http://www.ioccc.org/2000/tomx.hint
Comments