Как вывести __VA_ARGS__ не зная их типа в макросе на C

как вывести через fprintf() значения аргументов макроса __VA_ARGS__ не зная их типа

#define LOG(M, ...) fprintf(stderr, "[INFO] %s [%d]: %s /*__VA_ARGS__?*/\n", __FILE__, __LINE__, M)

Ответы (5 шт):

Автор решения: avp

Наверное проще всего в соответствии с описанием 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$ 
→ Ссылка
Автор решения: Pak Uula

Я бы делал не просто 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
→ Ссылка
Автор решения: Stanislav Volodarskiy

Стандартное решение. Типы есть, они спрятаны строке-шаблоне:

#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
→ Ссылка
Автор решения: Stanislav Volodarskiy

Да, можно печатать значения, не зная их типы. В 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.

→ Ссылка
Автор решения: AVI-crak Home

Не понимаю смысла использовать стандартные функции подмножества 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

→ Ссылка