michk4 Asked:2024-01-20 16:38:27 +0000 UTC2024-01-20 16:38:27 +0000 UTC 2024-01-20 16:38:27 +0000 UTC 如何在不知道 C 宏中的类型的情况下输出 __VA_ARGS__ 772 如何在不知道宏参数类型的情况下使用 fprintf() 显示宏参数 __VA_ARGS__ 的值 #define LOG(M, ...) fprintf(stderr, "[INFO] %s [%d]: %s /*__VA_ARGS__?*/\n", __FILE__, __LINE__, M) c 5 个回答 Voted Best Answer Stanislav Volodarskiy 2024-01-21T01:20:53Z2024-01-21T01:20:53Z 标准溶液。有一些类型,它们隐藏在模板行中: #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 PS编译器检查模板和参数是否匹配。如果从 中删除强制转换(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 avp 2024-01-20T23:22:26Z2024-01-20T23:22:26Z 根据可变参数宏的描述,最简单的方法可能是编写和使用类似的宏,如下所示: #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$ Stanislav Volodarskiy 2024-01-21T03:37:48Z2024-01-21T03:37:48Z 是的,您可以在不知道值类型的情况下打印值。出现在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 PS在我看来,使用类似的工具,我们可以根据参数类型组装一种格式并构造一个函数调用fprintf。现在有很多,将来也会有一个。 PS可以修改宏DUMP,以便它打印参数名称LOG及其值。需要编写的调试代码更少。例如:LOG(argc, argv[0]);将打印argc = 1 argv[0] = ./a.out. Pak Uula 2024-01-21T00:17:28Z2024-01-21T00:17:28Z 我不仅会这样做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 AVI-crak Home 2024-01-22T16:09:40Z2024-01-22T16:09:40Z 我不明白将标准 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/树/master/printo
标准溶液。有一些类型,它们隐藏在模板行中:
PS编译器检查模板和参数是否匹配。如果从 中删除强制转换
(void *)argv,将会出现诊断错误:根据可变参数宏的描述,最简单的方法可能是编写和使用类似的宏,如下所示:
编译并运行
是的,您可以在不知道值类型的情况下打印值。出现在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。PS在我看来,使用类似的工具,我们可以根据参数类型组装一种格式并构造一个函数调用
fprintf。现在有很多,将来也会有一个。PS可以修改宏
DUMP,以便它打印参数名称LOG及其值。需要编写的调试代码更少。例如:LOG(argc, argv[0]);将打印argc = 1 argv[0] = ./a.out.我不仅会这样做
LOG(M,...),LOG(FMT,...)还会FMT指定输出的格式。结果:
我不明白将标准 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/树/master/printo