О самодельном printf для идиотов, перегрузке функций в C и макросах

Я не люблю отладку и отладчики. Ну вот привычнее мне как-то вести логи и по ним уже разбираться где что не так.
В обычной жизни для этого использую либо printf, либо самодельные обертки над ним.

Но иногда приходится работать с AVR и прочей мелочевкой, там на полноценный printf как-то жаба давит память тратить.
Да и в целом хочется минимализма. И вот к чему я пришел.

Что именно я логирую?

Ну, во-первых просто трассировка функций — типа зашел туда-то, делаю то-то, и т.д.
И во-вторых, это числовые значения (коды ошибок, всякие значения переменных…).
И зачем, скажите, для этого printf?

Итак, от чего же можно отказаться:
* от вывода строк через %s — если надо — делаем просто несколько puts().
* от вывода одиночных символов через %c — аналогично, putc().
* от вывода floating-point чисел (да у нас и так fixed-point контроллер).
* от вывода чисел в десятичном виде — ну hexadecimal вполне легко читается, зачем эти операции деления на 10 и все такое?
* от вывода больше чем одного агрумента за один вызов printf-подобной функции.

Как это выглядит?

Итого, вместо printf(), пусть даже самого легкого, нам понадобятся две функции:

void log_string(const char *s) {
	for (; *s; s++) log_putc(*s);
}
void log_uint32(const char *s, uint32_t x) {
	int i;
	static const char hex[] = "0123456789abcdef";

	log_string(s);
	log_putc('0'); log_putc('x');
	for (i = 28; i >= 0; i = i - 4) {
		log_putc(hex[(x >> i) & 0xf]);
	}
}

Для тех же AVR разница достаточная: вместо >2Кб на printf имеем всего 295 байт (с -O0) на обе наши функции.

А при чем тут макросы?

А вот не хочу я думать о том, что у меня две функции. Хочу одну: LOG(string) или LOG(string, number). И еще чтобы без stdarg.h. Считайте это моей прихотью 🙂 Говорите невозможно? С макросами возможно все! (ПРИМЕЧАНИЕ: это очень плохой стиль, никогда так не делайте и не слушайте тех, кто говорит что с макросами возможно все).

Итак, нам очевидно нужен макрос LOG(…) с переменным числом аргументов (в нашем случае — 1 или 2). Почему мы не делаем LOG(s, …) — щас поймем. Данный макрос должен развернуться либо в log_string, либо в log_uint32 в зависимости от числа аргументов.

Т.е. суффикс функции зависит от числа параметров макроса: 1 = string, 2 = uint32. Для остальных случаев — ошибки при компиляции.

Применяем известный хак с определением числа аргументов:

#define VA_NARGS_IMPL(_1, _2, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, uint32, string)

VA_NARGS(s) -> string
VA_NARGS(s, x) -> uint32

VA_NARGS(s, x, y) -> y
VA_NARGS() -> string. Но нас это устраивает.

Нулевое число параметров можно обойти дополнительной проверкой: sizeof(#__VA_ARGS__) == sizeof(«»).
Есть и более правильные и сложные варианты: http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/
Но в нашем случае ошибку компилятор выстаст и так.

Теперь остается только добавить к имени функции полученный суффикс — и дело сделано:

#define VA_NARGS_IMPL(_1, _2, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, uint32, string)

#define LOG_IMPL2(suffix, ...) log_ ## suffix (__VA_ARGS__)
#define LOG_IMPL(suffix, ...) LOG_IMPL2(suffix, __VA_ARGS__) 
#define LOG(...) LOG_IMPL(VA_NARGS(__VA_ARGS__), __VA_ARGS__)

int main() {
	LOG();                   // ERR -> log_string (); 
	LOG("hello");            // OK  -> log_string ("hello");
	LOG("hello", b);         // OK  -> log_uint32 ("hello", b);
	LOG("hello", a, b);      // ERR -> log_b ("hello", a, b);
	LOG("hello", a, string); // ERR -> log_string ("hello", a, string);
	return 0;
}

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

И не вздумайте повотрять это в ваших проектах!

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s