Счётчики итераций.

Немного препроцессорной магии.

Иногда возникает необходимость сделать так, чтобы код выполнялся только первую или несколько первых итераций.
Решить эту проблему нетрудно, но порой, особенно при отладке методом отладочной печати ^^, когда подобные условия приходится вписывать в разных частях программы по десять раз, это дело хочеться несколько систематизировать.


Интернет, разумеется не подкачал и пестрит решениями следующего синтаксиса:
DO_ONCE
do_something();
DO_ONCE_END

Синтаксис тут неслишком удобный. Хочется варианта без DO_ONCE_END.
Разумеется, интернет предлагает и это:
do_once
do_something();

Вот от этого мы и отталкиваемся. Одна из возможных реализаций макроса однократного исполнения:
#define only_once \
    static int _concate(__internal_var, __LINE__) = 1; \
    if (_concate(__internal_var, __LINE__)) if (_concate(__internal_var, __LINE__)--) 	

only_once do_something();

_concate — это макрос конкатенации. В результате его работы создаётся уникальное имя для данного конкретного счетчика.
Такая механика позволяет использовать множество счетчиков в одном пространстве имён без необходимости задаватль вручную уникальные имена.

Дальше больше. Макрос, выполняющийся b первых итераций:
#define do_iteration(b) \
    static int _concate(__internal_var, __LINE__) = 0; \
    if (_concate(__internal_var, __LINE__) < b) if (++_concate(__internal_var, __LINE__)) 

do_iteration(10) do_something();

Логическое продолжение. Раз можно выполнять до, то можно выполнять и после. Код выполняется после b итераций:
#define do_after_iteration(b) static int _concate(__internal_var, __LINE__) = 0; \
	if (_concate(__internal_var, __LINE__) < b) ++_concate(__internal_var, __LINE__); else

do_after_iteration(10) do_something();


А ну как, если нужно что-то выполнить между 15 и 17 итерациями?
do_after_iteration(15) do_iteration(2) do_something(); //ошибка

Увы, не работает в силу устройства макросов. Нельзя записать два макроса в один блок, это приведёт к повторению имён переменных. Между макросами обязательно должны стоять или ';' или '{'.
Можно сделать так:
do_after_iteration(15) {do_iteration(2) do_something();};

Но это неслишком красиво, потому еще один макрос:
#define do_between_iteration(a, b) static int _concate(__internal_var, __LINE__) = 0; \
	if (_concate(__internal_var, __LINE__) < a) ++_concate(__internal_var, __LINE__); else \
	if (_concate(__internal_var, __LINE__) < b) if (++_concate(__internal_var, __LINE__))	

do_between_iteration(15,17) do_something();


А что если я хочу сделать ветку. Допустим до такой-то итерации выполнять один код, а после другой.
И это можно благодаря «недокументированным возможностям» макросов only_once, do_iteration, do_between_iteration.
do_iteration(10) do_something(); else; else do_something_else();


Не слишком красиво, а потому еще один макрос:
#define do_after else;else

do_iteration(10) do_something(); do_after do_something_else();


Уже гораздо лучше, как по мне. С помощью данных макросов можно писать довольно сложные ветвления, правда без блоков уже не обойдётся. Например,
do_between_iteration(6,20) 
	do_something(); 
do_after {
	do_iteration(5) 
		do_something_else(); 
	do_after halt();};

Эта конструкция выполняет do_something() от 6-ой до 19-ой итерации включительно, после чего следующие пять итераций делает do_something_else(), после чего вешает систему в функции halt().

Я полагаю, что на этом месте покрыты 99% и так не слишком полезного функционала, но работа не завершена. Если необходимо запрограммировать более сложное ветвление, то, пользуясь указанным выше способом можно неслабенькую лапшу.

К этой задаче, впрочем можно подойти немого с другого конца:

struct iteration_counter
	{
		long long iteration;
		long long helper;
	};
	
	#define iter_start(obj) \
	static iteration_counter obj = {0, 0}; \
	obj.iteration++;\
	switch(obj.helper){case 0:
	
	#define iter(obj,a) if (obj.iteration == a) obj.helper = a; break; case a:
	
	#define iter_end(obj,a) if (obj.iteration == a) obj.helper = a; break; default: break; }
	
	#define iter_end_unstoped(obj) if (obj.iteration >= obj.helper) obj.iteration = obj.helper; break;}

Данный «процессор итераций» построен на switch-case. user код выглядит так:
iter_start(obj)     do_something0();	//выполнять до второй итерации 
iter(obj,2)         do_something1();	//выполнять со второй до четвертой итерации
iter(obj,4)         do_something2();	//выполнять с четвертой по десятую итерации
iter_end(obj,10);


Есть вариант макроса

iter_end_unstoped(obj)

, использование которого приветдет к тому, что последняя функция продолжит выполняться до конца времён.

Этому набору макросов передаётся незанятое в области видимости имя, на которое макрос iter_start вешает свой счётчик.
К сожалению, мне не удалось придумать способ, при котором не нужно было бы максить это имя в подчинённые макросы, не, тем более, способа, при котором можно было вообще обойтись без имени. Если есть такой вариант, поделитесь. У меня всё, спасибо за внимание.

Архив с хеадером прилагается.
  • +3
  • 09 декабря 2015, 04:53
  • Mirmik
  • 1
Файлы в топике: iteration_counter.zip

Комментарии (10)

RSS свернуть / развернуть
Интересная вещь, но хотелось бы увидеть практический пример использования. А так же ассемблер сабжа, во что эти итераторы разворачиваются.
0
ОЙ, не думаю, что разворачиваются они во что-то хорошее. Как я сказал, в первую очередь это для отладки. А эта задача производительности не требует.

Что до примера… Не дай бог у меня появиться потребность это использовать…
+1
Пожалуйста, пример использования.
Тестирую диспетчер.

void task1()
{
do_iteration(5) os << «fdafa\n»;
only_once scheduler()->process_set_wait(current_process());
};

void setup() {
automSched.registry(task1);
}

void loop() {
automSched.schedule();

do_between_iteration(16,17) scheduler()->process_set_running(current_process());
}

Проверяю систему перевода задачи в состояние wait с последующим переводом в состояние running.
+1
Спасибо. Я так и думал, что это не для «боевого» кода, а для отладочного.
0
Счетчики итераций бывают полезны и в «боевом» коде. Но там, обычно, также необходимо иногда обнулять их… А сделать это невозможно, когда имя переменной инкапсулировано в макрос. Да и не нужно это, потому что Приведенные выше макросы призваны решать проблему ручного спама имен отладочных переменных. Если же это имя так и так надо объявить, то почему бы не пользоваться простыми конструкциями if(iteration < 5). То есть, надобность сама собой отпадает.
0
Забавная идея.
#define do_iteration(b) \
    static int _concate(__internal_var, __LINE__) = 0; \
    if (_concate(__internal_var, __LINE__) < b) if (++_concate(__internal_var, __LINE__)) 

Еще можно реализовать по аналогии с only_once:
#define do_iterations(a) \
    static int _concate(__internal_var, __LINE__) = a; \
    if (_concate(__internal_var, __LINE__)) if (_concate(__internal_var, __LINE__)--)
0
  • avatar
  • Vga
  • 09 декабря 2015, 22:55
по своему опыту и опыту коллег скажу, что макросы, подобные приведённым в статье, причиняют невыносимую жопоболь при отладке.
+3
А можно поподробнее?
0
Для подобных целей я как-то завёл макрос:
#define UNIQUE(x) _concate(x,__LINE__)


А так да, do_once полезная вещь при отладке
0
У меня такая штука называется _var_generate(a)
#define _var_generate(a) _concate(_concate(__internal_var, __LINE__),a)

А еще есть просто шикарный, который присутствуя во всех остальных отладочных макросах просто напросто исключает возможность ситуации потерянного макроса :).
#define debug_place() if(DEBUG_PLACE) {ATOMIC(_var_generate(1)); debug_print(__FILE__); debug_putchar(':'); debug_printdec_uint64(__LINE__); debug_putchar(':'); debug_print(__FUNCTION__); debug_putchar('\n'); debug_delay(DEBUG_PLACE_TIMER); DEATOMIC(_var_generate(1));}
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.