如何将 (void**) 传递给任何类型的函数并高效地 Dereference/Use?

How to Pass (void**) to Function and Efficiently Dereference/Use for Any type?

使用一个简单的函数来打印模拟的二维矩阵,我只是希望能够将指针数组传递给函数 void**,以及所需的维度 m x nsizeof type 格式字符串 用于 printf,以及一个简单的标志来区分 浮点数 整数。我 运行 遇到的问题是处理取消引用指针数组的范围,以便每个元素都可以正确打印为原始类型。下面,基本方案是:

void mtrx_prnv (size_t m, size_t n, void **matrix,
                size_t sz, char *fmt, char fp)
{
    if (fp) {    // floating point
        if (sz == 4) {
            float **mtrx = (float **)matrix;
            ...
        }
        else if (sz == 8) {
            double **mtrx = (double **)matrix;
            ...
        }
    }
    else {
        if (sz == 1) {
            char **mtrx = (char **)matrix;
            ...
        }
        else if (sz == 2) {
            ...
        }
        ...
    }
}

该方法工作正常,但问题是在测试浮点标志 'fp' 和 sizeof 类型 'sz' 之后,任何用于取消引用的指针创建都限制在范围内测试块并最终要求对处理每个块中的指针数组所需的代码进行基本逐字复制。代码最终比为每种类型创建许多小函数要长。例如:

void mtrx_prn_float (size_t m, size_t n, float **matrix) {}

void mtrx_prn_int (size_t m, size_t n, int **matrix) {}

是否有更好的或标准的方法来将指针数组传递给类型 void**、sizeof 类型以及正确取消对类型的引用所需的任何其他标志,而无需重复代码?如果这是必须的方式,那很好,但我想确保我没有错过一个简单的技巧。到目前为止,我的搜索没有产生任何有用的结果(搜索返回的大量信息是关于如何取回单一类型,而不是分离所有类型)。 How to write C function accepting (one) argument of any type 此处不正确。

完整的功能(没有 int/unsigned 分离的附加逻辑)如下。

/* printf array of pointers to type as 2D matrix of
   size 'm x n' with sizeof type 'sz', format string
  'fmt' and floating point flag 'fp' (0 - int).
*/
void mtrx_prnv (size_t m, size_t n, void **matrix,
                size_t sz, char *fmt, char fp)
{
    register size_t i, j;

    if (fp) {    /* floating point */

        if (sz == 4) {
            float **mtrx = (float **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 8) {
            double **mtrx = (double **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else
            goto err;
    }
    else {      /* integer (no unsigned yet) */

        if (sz == 1) {
            char **mtrx = (char **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 2) {
            short **mtrx = (short **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 4) {
            int **mtrx = (int **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 8) {
            long **mtrx = (long **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else
            goto err;
    }

    return;

err:;

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n",
                __func__, sz);
}

根据评论中的建议,declaration/definition 更新为:

void mtrx_prnv (size_t m, size_t n, void *matrix,
                size_t sz, char *fmt, char fp);

宏提供的解决方案

提出了几个很好的解决方案,主要是通过为冗余文本创建宏来消除逐字​​重复,其次是使用回调来打印元素的值。宏提供了接近下降的解决方案:

#define PRINT_MATRIX(type) do { \
    type **mtrx = (type **)matrix;  \
    for (i = 0; i < m; i++) { \
        char *pad = " [ "; \
        for (j = 0; j < n; j++) { \
            printf (fmt, pad, mtrx[i][j]); \
            pad = ", "; \
        } \
        printf ("%s", " ]\n"); \
    } \
} while (0)

...

void mtrx_prnvm (size_t m, size_t n, void *matrix,
                size_t sz, char *fmt, char fp)
{
    register size_t i, j;

    if (fp) {    /* floating point */

        if (sz == 4) {
            PRINT_MATRIX(float);
        }
        else if (sz == 8) {
            PRINT_MATRIX(double);
        }
        else
            goto err;
    }
    else {      /* integer (no unsigned yet) */

        if (sz == 1) {
            PRINT_MATRIX(char);
        }
        else if (sz == 2) {
            PRINT_MATRIX(short);
        }
        else if (sz == 4) {
            PRINT_MATRIX(int);
        }
        else if (sz == 8) {
            PRINT_MATRIX(long);
        }
        else
            goto err;
    }

    return;

err:;

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n",
                __func__, sz);
}

最简单的解决方案可能是定义一个宏,该宏在被调用时为指定类型定义一个函数(但我个人仍然倾向于为每种类型编写单独的函数;宏总是感觉像是一种廉价的 hack我)。类似于:

#define define_matrix_print(type,fmt) \
    void mtrx_prn_##type(size_t m, size_t n, type **matrix) { \
       for (int i = 0; i < m; i++) { \
           char *pad = " [ "; \
           for(int j = 0; j < n; j++) { \
               printf(fmt, pad, matrix[i][j]); \
               pad = ", "; \
           } \
           printf(" ]\n"); \
       } \
     }

define_matrix_print(char, "%c")
/* above defines 'mtrx_prn_char(size_t, size_t, char **)' */
define_matrix_print(int, "%d")
/*               'mtrx_prn_int(size_t, size_t, int **)'   */
/*    and so on.                                           */

另一种可能性是使用函数指针,例如:

void (*print_func)(void *mtrx, int i, int j, char *fmt, char *pad);
switch (sz) {
    case 1:
        print_func = print_char_mtrx;
        break;
    case 2:
        print_func = print_short_mtrx;
        break;
    // others omitted for brevity
}
for (i = 0; i < m; i++) {
    char *pad = " [ ";
    for (j = 0; j < n; j++) {
        print_func(matrix, i, j, fmt, pad);
        pad = ", ";
    }
    printf ("%s", " ]\n");
}

然后你声明:

void print_char_mtrx(void *mtrx, int i, int j, char *fmt, char *pad)
{
    char ** mtrx_c = mtrx;
    printf(fmt, pad, mtrx_c[i][j]);
}

... 其他类型依此类推。但是,您只能通过这种方式节省一点点 typing/duplication。您当然可以选择结合这两种技术并定义一个宏,您可以使用该宏来定义各种函数以从各种类型的矩阵中打印元素。

这有点像 XY 问题,代码重复也不太新鲜。所以我建议完全使用不同的策略。请改用回调。

mtrx_prnv 函数遍历所有索引,但使用提供的回调来打印实际值。回调接收索引和矩阵作为 void *(就像 mtrx_prnv 本身),但将其转换为适当的类型并打印值。

void mtrx_prnv(size_t n, size_t m, void * mtrx, void (*prncb)(size_t i, size_t j, void * mtrx)){
    size_t i, j;
    for (i = 0; i < m; i++) {
        printf(" [ ");
        for (j = 0; j < n; j++) {
            prncb(i, j, mtrx); /* Use callback to print value */
            printf(", "); /* fix the trailing comma yourself ;) */
        }
        printf (" ]\n");
    }
}

ints 和 floats 的回调示例:

void prnint(size_t i, size_t j, void * vmat){
    int ** mtrx = vmat;
    printf("%d", mtrx[i][j]);
}

void prnflt(size_t i, size_t j, void * vmat){
    float ** mtrx = vmat;
    printf("%f", mtrx[i][j]);
}

由调用者为矩阵类型提供正确的回调,但这意味着 mtrx_prnv 不需要关心实际类型,甚至可以处理更奇特的内容,你只需要提供适当的回调即可。

为了便于阅读,您可以 typedef 函数指针:

typedef void (prncb_f)(size_t i, size_t j, void * mtrx);

并更改mtrx_prnv

void mtrx_prnv(size_tn, size_t m, void * mtrx, prncb_f * cb)

添加

如果您需要保留 mtrx_prnv 的签名,您可以在其中执行选择器逻辑并让它传递适当的回调:

(将之前的 mtrx_prnv 重命名为 mtrx_prnv_helper

void mtrx_prnv (size_t m, size_t n, void *matrix,
            size_t sz, char *fmt, char fp){
    if(fp){
        if(sz == sizeof(float)){
            mtrx_prnv_helper(m, n, matrix, prnflt);
        }else if(sz == sizeof(double)){
            ...
        }
    }else{
        if(sz == sizeof(int)){
            mtrx_prnv_helper(m, n, matrix, prnint);
        }else{
            ...
        }
    }
}

但是您将失去矩阵中其他数据类型的潜力。

当然,为矩阵中的每个单元格调用回调函数会产生一些函数调用开销,但在这种情况下,它很可能被 I/O-overhead 打印所掩盖。

考虑到您要逐字重复每个块的大部分内容,一个明显的解决方案是定义和使用表示该代码的宏(而不是另一个答案建议的用于生成每种类型函数的宏)。这将限制物理代码重复,并且不需要您更改程序的结构:

#define PRINT_MATRIX(type) do { \
    (type) **mtrx = ((type) **)matrix;
    for (i = 0; i < m; i++) { \
        char *pad = " [ "; \
        for (j = 0; j < n; j++) { \
            printf (fmt, pad, mtrx[i][j]); \
            pad = ", "; \
        } \
        printf ("%s", " ]\n"); \
    } \
} while (0)

void mtrx_prnv (size_t m, size_t n, void *matrix,
                size_t sz, char *fmt, char fp)
{
    if (fp) {    // floating point
        if (sz == 4) {
            PRINT_MATRIX(float);
        }
        else if (sz == 8) {
            PRINT_MATRIX(double);
        }
    }
    else {
        if (sz == 1) {
            PRINT_MATRIX(char);
        }
        else if (sz == 2) {
            PRINT_MATRIX(short);
        }
        ...
    }
}

以下实现通过使用缓冲区变量避免了重复。

只要是printf的最后一个参数,任何类型长度都可以同样支持。 t必须是最大的类型。

void mtrx_prnv(size_t m, size_t n, void *matrix,
               size_t sz, char *fmt, char fp)
{
    size_t i, j;

    if (sz != 4 && sz != 8 && (fp || (sz != 1 && sz != 2))) {
        fprintf(stderr, "%s() error: invalid size for fp_flag '%zu'.\n",
            __func__, sz);
        return;
    }

    for (i = 0; i < m; i++) {
        char *p = ((char **)matrix)[i];
        double t;

        memcpy(&t, p, sz);
        printf(fmt, " [ ", t);
        for (j = sz; j < n * sz; j += sz) {
            memcpy(&t, p + j, sz);
            printf(fmt, ", ", t);
        }
        printf(" ]\n");
    }
}

使用 memcpy 而不是直接强制转换是为了避免极端情况,例如较大的值位于两个页面之间的边界,这可能会导致段错误。它可以优化为有条件地使用,并且只在每个页面的最后一个元素中使用。

虽然问题已经回答了,但我想展示一个替代方案。这也使用宏来简化代码,但它是在行循环中使用的(因为打印任何东西,即使是单个数字,也比条件语句花费的时间长得多)。另外,函数参数有点不同。

#include <stdio.h>

typedef enum {
    UNSIGNED_CHAR,
    SIGNED_CHAR,
    UNSIGNED_SHORT,
    SIGNED_SHORT,
    UNSIGNED_INT,
    SIGNED_INT,
    UNSIGNED_LONG,
    SIGNED_LONG,
    FLOAT,
    DOUBLE
} element_type;

void fmatrix(FILE *const        out,
             void *const        origin,
             const int          rows,
             const int          cols,
             const long         rowstride,
             const long         colstride,
             const element_type type,
             const int          decimals)
{
    int row, col;

    for (row = 0; row < rows; row++) {

        switch (type) {
#define PRINTROW(type, argtype, spec) \
            do { \
                const unsigned char *const data = (unsigned char *)origin + row * rowstride; \
                for (col = 0; col < cols - 1; col++) \
                    fprintf(out, spec ", ", decimals, (argtype)( *(const type *)(data + col * colstride) )); \
                fprintf(out, spec "\n", decimals, (argtype)( *(const type *)(data + (cols - 1) * colstride) )); \
            } while (0)

        case UNSIGNED_CHAR:  PRINTROW(unsigned char,  unsigned int,  "%*u");  break;
        case UNSIGNED_SHORT: PRINTROW(unsigned short, unsigned int,  "%*u");  break;
        case UNSIGNED_INT:   PRINTROW(unsigned int,   unsigned int,  "%*u");  break;
        case SIGNED_CHAR:    PRINTROW(signed char,    int,           "%*d");  break;
        case SIGNED_SHORT:   PRINTROW(signed short,   int,           "%*d");  break;
        case SIGNED_INT:     PRINTROW(int,            int,           "%*d");  break;
        case UNSIGNED_LONG:  PRINTROW(unsigned long,  unsigned long, "%*lu"); break;
        case SIGNED_LONG:    PRINTROW(long,           long,          "%*ld"); break;
        case FLOAT:          PRINTROW(float,          double,        "%.*f");  break;
        case DOUBLE:         PRINTROW(double,         double,        "%.*f");  break;

#undef PRINTROW
        }
    }
}

origin指向矩阵左上角的元素。该矩阵有 rows 行和 cols 列。

colstriderowstride 定义矩阵中连续成员之间的字节数。对于标准 C 数组,rowstride = cols * colstridecolstride = sizeof (type),对于 type.

类型的矩阵元素

如果你想要转置输出,只需交换 rowscols,以及 rowstridecolstride

eltype指定元素类型,decimalsFLOATDOUBLE类型的小数位数(默认为6),或最小宽度整数类型。如果您不想指定,请使用 -1(因为负精度被视为被省略)。

举个实际例子,

float m[2][3] = { { 1, 2, 3 }, { 4, 5, 6 }};

fmatrix(stdout, m, 2, 3, 3 * sizeof m[0][0], sizeof m[0][0], FLOAT, 2);

产出

1.00, 2.00, 3.00
4.00, 5.00, 6.00

fmatrix(stdout, m, 3, 2, sizeof m[0][0], 3 * sizeof m[0][0], FLOAT, 1);

产出

1.0, 4.0
2.0, 5.0
3.0, 6.0

我使用的 PRINTROW 宏有点难看和复杂(可以写得更好),但这应该是完全可移植的代码,仔细编写以明确地做正确的 casts/promotions,所以希望它足够清楚理解。