Как вывести __VA_ARGS__ не зная их типа в макросе на C
как вывести через fprintf() значения аргументов макроса __VA_ARGS__ не зная их типа
#define LOG(M, ...) fprintf(stderr, "[INFO] %s [%d]: %s /*__VA_ARGS__?*/\n", __FILE__, __LINE__, M)
Ответы (5 шт):
Наверное проще всего в соответствии с описанием Variadic Macros написать и использовать подобный макрос следующим образом:
#include <stdio.h>
#include <stdlib.h>
#define LOG(M, FMT...) ({ \
int l = fprintf(stderr, "[INFO] %s [%d]: %s ", \
__FILE__, __LINE__, M); \
if (l > 0) { \
l += fprintf(stderr, FMT); \
fputc('\n', stderr); \
++l; \
} \
l; })
int
main (int ac, char *av[])
{
LOG("Start", " ");
LOG("Args:", "ac = %d av = %p", ac, av);
return puts("End") == EOF;
}
Компилируем и запускаем
avp@avp-desktop:~/avp/hashcode$ gcc t1.c && ./a.out 1 2 3
[INFO] t1.c [20]: Start
[INFO] t1.c [21]: Args: ac = 4 av = 0x7ffc2f503168
End
avp@avp-desktop:~/avp/hashcode$
Я бы делал не просто LOG(M,...), а LOG(FMT,...), где FMT задаёт форматирование вывода.
#include <stdarg.h>
#include <stdio.h>
#define LOG(FMT, ...) fprintf(stderr, "[INFO] %s [%d]: " FMT "\n", __FILE__, __LINE__ __VA_OPT__(, ) __VA_ARGS__)
int main(int argc, char *argv[])
{
LOG("Start");
LOG("Args: argc == %d, argv == %p", argc, argv);
// Компилятор сообщит об ошибке: format ‘%d’ expects a matching ‘int’ argument ... in expansion of macro ‘LOG’
// LOG("Mismatch: argc == %d, argv == %p");
}
Результат:
[INFO] some.c [8]: Start
[INFO] some.c [9]: Args: argc == 1, argv == 0x7ffc2cc8f148
Стандартное решение. Типы есть, они спрятаны строке-шаблоне:
#include <stdio.h>
#define LOG(...) \
do { \
fprintf(stderr, "INFO %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
} while (0)
int main(int argc, char *argv[]) {
LOG("start");
LOG("argc = %d, argv = %p", argc, (void *)argv);
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion var-args.c $ ./a.out INFO var-args.c:13: start INFO var-args.c:14: argc = 1, argv = 0x7fff8c0e1a88
P.S. Компилятор проверяет соответствие шаблона и аргументов. Если убрать приведение из (void *)argv, то будет ошибка с диагностикой:
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion var-args.c var-args.c: In function ‘main’: var-args.c:14:9: error: format ‘%p’ expects argument of type ‘void *’, but argument 4 has type ‘char **’ [-Werror=format=] 14 | LOG("argc = %d, argv = %p", argc, argv); | ^~~~~~~~~~~~~~~~~~~~~~ ~~~~ | | | char ** var-args.c:8:25: note: in definition of macro ‘LOG’ 8 | fprintf(stderr, __VA_ARGS__); \ | ^~~~~~~~~~~ var-args.c:14:29: note: format string is defined here 14 | LOG("argc = %d, argv = %p", argc, argv); | ~^ | | | void * cc1: all warnings being treated as errors
Да, можно печатать значения, не зная их типы. В C11 появился generic selection. С помощью него компилятор выбирает функцию по типу аргумента. В C нет перегрузки функций, а generic selection позволяет её имитировать.
Макрос DUMP печатает значение любого из трёх типов: int, char * и const char *. Можно добавлять ещё типы, я ограничился тремя для примера.
Макросы DUMP1 - DUMP9 раскрываются в цепочку вызовов DUMP. Каждый макрос обрабатывает аргументы согласно его номеру. Например: DUMP4(1, 2, 3, 4) раскроется в DUMP(1); DUMP(2); DUMP(3); DUMP(4);
Макрос DUMPX выбирает один из номерных макросов и применяет его к своим аргументам. Например DUMPX(1, 2, 3, 4) раскроется в DUMP(1); DUMP(2); DUMP(3); DUMP(4);.
Макрос LOG выводит от одного до девяти аргументов.
Это полностью стандартный код для C11. Если вы решитесь его использовать, рекомендую ко всем макросам, кроме LOG, добавить префиксы, чтобы уменьшить шанс пересечения с другими библиотеками. Например: LOG_IMPL_DUMP, LOG_IMPL_DUMP1 - LOG_IMPL_DUMP9, LOG_IMPL_DUMPX, LOG_IMPL_TENTH.
#include <stdio.h>
void dump_int(int v) { fprintf(stderr, " %d", v); }
void dump_pchar(const char *v) { fprintf(stderr, " %s", v); }
#define DUMP(X) \
_Generic( \
(X) , \
int : dump_int , \
char *: dump_pchar, \
const char *: dump_pchar \
)(X)
#define TENTH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10
#define DUMP1(V1) DUMP(V1);
#define DUMP2(V1, ...) DUMP1(V1) DUMP1(__VA_ARGS__)
#define DUMP3(V1, ...) DUMP1(V1) DUMP2(__VA_ARGS__)
#define DUMP4(V1, ...) DUMP1(V1) DUMP3(__VA_ARGS__)
#define DUMP5(V1, ...) DUMP1(V1) DUMP4(__VA_ARGS__)
#define DUMP6(V1, ...) DUMP1(V1) DUMP5(__VA_ARGS__)
#define DUMP7(V1, ...) DUMP1(V1) DUMP6(__VA_ARGS__)
#define DUMP8(V1, ...) DUMP1(V1) DUMP7(__VA_ARGS__)
#define DUMP9(V1, ...) DUMP1(V1) DUMP8(__VA_ARGS__)
#define DUMPX(...) \
TENTH(__VA_ARGS__, DUMP9, DUMP8, DUMP7, DUMP6, DUMP5, DUMP4, DUMP3, DUMP2, DUMP1, _)(__VA_ARGS__)
#define LOG(...) \
do { \
fprintf(stderr, "INFO %s:%d:", __FILE__, __LINE__); \
DUMPX(__VA_ARGS__) \
fputc('\n', stderr); \
} while (0)
int main(int argc, char *argv[]) {
LOG("start");
LOG("argc =", argc, "argv[0] =", argv[0]);
LOG(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion var-args.c $ ./a.out INFO var-args.c:39: start INFO var-args.c:40: argc = 1 argv[0] = ./a.out INFO var-args.c:41: 1 2 3 4 5 6 7 8 9
P.S. Думается мне, что можно с помощью аналогичных средств собрать формат по типам аргументов и построить один вызов функции fprintf. Сейчас их много, будет один.
P.S. Макрос DUMP можно доработать так, чтобы он печатал и имя параметра LOG и его значение. Меньше кода писать для отладки. Например: LOG(argc, argv[0]); напечатает argc = 1 argv[0] = ./a.out.
Не понимаю смысла использовать стандартные функции подмножества printf() совместно с макросом _Generic(). Это как купить билет на самолёт, и отправиться в путь пешком. Вот готовое printo("text", double, float, uint(8-16-32-64)_t, int(8-16-32-64)_t ) https://github.com/AVI-crak/Rtos_cortex/tree/master/printo