【问题标题】:Wrapping variadic arguments in a c macro在 c 宏中包装可变参数
【发布时间】:2022-11-05 16:18:28
【问题描述】:

所以,我想创建一个函数(类似宏),它接受任意数量的不同类型的参数并对它做一些事情。 我的意思是,我确实设法让它工作,但我正在寻找一个更优雅的解决方案(或确保我的方式是它应该看起来的方式)。

函数宏 print(...) 的示例代码:

#ifndef EVIL_PRINT_H
#define EVIL_PRINT_H

#include <stdio.h>

#define TWENTY_SECOND_ARGUMENT(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, ...) _22

#define COUNT_ARGUMENTS(...) TWENTY_SECOND_ARGUMENT(__VA_ARGS__, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define PRINT_CHAR_EVIL(x) printf("%c", x)
#define PRINT_INT_EVIL(x) printf("%i", x)
#define PRINT_FLOAT_EVIL(x) printf("%f", x)
#define PRINT_DOUBLE_EVIL(x) printf("%d", x)
#define PRINT_PTR_EVIL(x) printf("%p", x)
#define PRINT_STR_EVIL(x) printf("%s", x)

#define PRINT_ONE_EVIL(x, ...) _Generic(x, \
            char: PRINT_CHAR_EVIL(x), \
            int: PRINT_INT_EVIL(x), \
            float: PRINT_FLOAT_EVIL(x), \
            double: PRINT_DOUBLE_EVIL(x), \
            void *: PRINT_PTR_EVIL(x), \
            char const *: PRINT_STR_EVIL(x), \
            char *: PRINT_STR_EVIL(x) \
            )
#define PRINT_TWO_EVIL(_1, _2, ...) PRINT_ONE_EVIL(_1); PRINT_ONE_EVIL(_2)
...
#define PRINT_TWENTY_ONE_EVIL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, ...) PRINT_TWENTY_EVIL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20); PRINT_ONE_EVIL(_21)

#define print(...) do { \
    switch (COUNT_ARGUMENTS(__VA_ARGS__)) { \
        default:break; \
        case 1: \
        PRINT_ONE_EVIL(__VA_ARGS__); \
        break; case 2: \
        PRINT_TWO_EVIL(__VA_ARGS__, 2); \
... \
        break; case 21: \
        PRINT_TWENTY_ONE_EVIL(__VA_ARGS__, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21); \
    } \
    } while(0);


#endif

我对这种方法的问题是它为一个调用复制了很多代码,但我不知道,也许编译器优化了所有不需要的分支。还有一个限制是它不会接受超过 x(在我的情况下 - 21)数量的参数。添加更多参数没什么大不了的,但是如果您需要 100 多个参数,文件大小将会增加。

使用示例:

#include "evil_print.h"

int main(void) {
    struct {
        int i;
        char c;
    } v = {.i = 100, .c = 'A'}, o;
    o = v;
    print(v.i, ", ", o.c);
}

是的,我知道在 c++ 中很容易,这里不要提及该语言。

【问题讨论】:

  • C++ 需要为可变参数宏添加特殊语法是有原因的——使用 C 宏并不容易。
  • 对于实际编译的代码,使用预处理器输出查看。 ——无论如何,你的用例是什么? 40 多年的 C 编程,显然数以百万计的程序员认为没有迫切需要这种结构。好的,我可以用锤子在墙上拧螺丝,但它是高质量工作的正确工具吗?
  • 我确实检查了预处理器的输出,这是我所期望的(我真的希望编译器在编译可执行文件时优化分支)。是的,当然,其他人不需要它,但拥有它很方便。

标签: c generics macros variadic-macros


【解决方案1】:

我最终结束用于https://gitlab.com/Kamcuk/yio/ 库的最优雅的解决方案是传递一个取决于参数类型的函数指针数组。这样的数组可以用复合文字在宏扩展内部构造。因为您可以通过指向函数的指针传递va_list 然后对其进行处理,所以您可以在函数指针内执行特定于类型的操作。该设计是为了将预处理器的使用限制在最低限度,尽可能早地分派到函数C端。

#include <stdarg.h>
#include <stdio.h>
#include <limits.h>

#define APPLYFOREACH_1(f,a)  f(a)
#define APPLYFOREACH_2(f,a,...)  f(a) APPLYFOREACH_1(f,__VA_ARGS__)
#define APPLYFOREACH_3(f,a,...)  f(a) APPLYFOREACH_2(f,__VA_ARGS__)
#define APPLYFOREACH_4(f,a,...)  f(a) APPLYFOREACH_3(f,__VA_ARGS__)
#define APPLYFOREACH_N(_4,_3,_2,_1,N,...)
        APPLYFOREACH##N
#define APPLYFOREACH(f, ...) 
        APPLYFOREACH_N(__VA_ARGS__,_4,_3,_2,_1)(f, ##__VA_ARGS__)

// ---------------------------------------------
// The main logic dispatcher.

typedef int (*lib_print_t)(va_list *va);

int lib_print_in(const lib_print_t *printers, ...) {
    va_list va;
    va_start(va, printers);
    for (; *printers != NULL; ++printers) {
        (*printers)(&va);
    }
    va_end(va);
    return 0;
}

// ---------------------------------------------
// Type specific printers.

int lib_print_int(va_list *va) {
    int c = va_arg(*va, int);
    return printf("%d", c);
}

int lib_print_char(va_list *va) {
    char c = va_arg(*va,
        // char is promoted to int.... or is it?
        // There are _many_ such cases to handle.
#if CHAR_MAX > INT_MAX
        unsigned int
#else
        int
#endif
    );
    return printf("%c", c);
}

int lib_print_charp(va_list *va) {
    const char *c = va_arg(*va, char *);
    return printf("%s", c);
}

int lib_print_float(va_list *va) {
    // but float _is_ promoted to double
    float c = va_arg(*va, double);
    return printf("%f", c);
}

#define DISPATCH(x) 
    _Generic((x) 
    , int: lib_print_int 
    , char: lib_print_char 
    , char*: lib_print_charp 
    , const char *: lib_print_charp 
    , float: lib_print_float 
    /* Note - comma on the end for below */ 
    ),

// ---------------------------------------------

// Calls lib_print_in with an array of function pointers
// and arguments.
#define lib_print(...) 
        lib_print_in( 
            (const lib_print_t []){ 
                APPLYFOREACH(DISPATCH, __VA_ARGS__) 
            }, 
            ##__VA_ARGS__ 
        )

// ---------------------------------------------

int main() {
    lib_print(1, ", ", 1.0f, (char)'
');
}

代码输出1, 1.000000

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    相关资源
    最近更新 更多