如何构造一个带有 void 指针参数的 C 函数并在运行时有条件地将它们转换为其他类型?

How to construct a C function with void pointer parameters and conditionally cast them to other types at runtime?

我正在尝试创建一个函数,其中参数作为 void 指针传递,并包含一个参数来设置 void 指针将转换为的数据类型,以便该函数可以用于不同类型。像下面这样的东西不起作用:

void test_function(int use_type, void * value, void * array) {
    // Set types to the parameters based on 'use_type'
    if (use_type == 0) { // Int type
        int * valueT = (int *) value;
        int * arrayT = (int *) array;
    } else if (use_type == 1) { // Double type
        double * valueT = (double *) value;
        double * arrayT = (double *) array;
    }
    // Main code of the program, setting an array item, regardless of type
    arrayT[0] = *valueT;
}

上面的代码有两个问题:正确输入的 valueTarrayT 在条件块中的范围内,并且对代码的主要部分不可见。将他们的声明移出块在给定的代码结构中是不可行的,因为他们需要不同的名称 intdouble 版本,打败了我的整个想法试图实现。另一个问题是 valueTarrayT 是函数局部的。我真正想要的是设置参数arrayarray[0] = *value.

看来我想做的事情在 C 中是不可能的...有没有办法可以做到这一点?

编辑:

数组行的赋值是为了演示我想做什么,那部分有很多代码。除了 intdouble 之外,还会有许多其他类型。将分配行移动到块中将意味着太多代码重复。

您问题的直接答案是在指针有效的块中取消引用赋值:

void test_function(int use_type, void * value, void * array) {
    // Set types to the parameters based on 'use_type'
    if (use_type == 0) { // Int type
        int * valueT = value, *arrayT = array; //the casts in C are unnecessary
        arrayT[0] = *valueT;
    } else if (use_type == 1) { // Double type
        double * valueT = value, *arrayT = array;
        arrayT[0] = *valueT;
    }
}

但您可能应该内联执行此操作,无需任何类型<->int 转换:

(type*){array}[0] = *(type*){value} //could make it DRY with a macro

不用传递类型标识符,传递对象的大小就足够了,也更简单:

void test_function( size_t sizeof_type, void* value, void* array ) 
{
    size_t element_index = 0 ; // for example

    memcpy( (char*)array + element_index * sizeof_type, value, sizeof_type ) ; 
}

为了保持类型不可知并保持您似乎想要的使用灵活性,您需要将 "main code" 移动到一个宏中并为每种情况调用它:

typedef enum {
    USE_TYPE_INT = 0,
    USE_TYPE_DOUBLE = 1,
    // ...
} USE_TYPE;

void test_function(USE_TYPE use_type, void * value, void * array) {

#define TEST_FUNCTION_T(type) do { \
    type * valueT = value;         \
    type * arrayT = array;         \
    /* Main code of the program */ \
    arrayT[0] = *valueT;           \
    /* ... */                      \
} while(0)

    // Set types to the parameters based on 'use_type'
    switch (use_type) {
        case USE_TYPE_INT:
            TEST_FUNCTION_T(int);
            break;
        case USE_TYPE_DOUBLE:
            TEST_FUNCTION_T(double);
            break;
        // ...
    }

#undef TEST_FUNCTION_T

}

请注意,虽然您只定义了一次 TEST_FUNCTION_T 宏,但每次使用都会导致重复的代码块,不同之处仅在于编译程序时粘贴到宏调用中的 type

您正试图在 C 中实现 polymorphism。沿着这条路走下去就是疯狂、无法维护的代码和新的编程语言。

相反,我强烈建议重构您的代码以使用更好的方法处理混合数据。 unionstruct 或指针或 any of the solutions here。这将减少长 运行 中的工作,并导致更快、更易于维护的代码。

或者你可以 switch to C++ and use templates.

或者您可以使用其他人的实现,例如 GLib's GArray. This is a system of clever macros and functions to allow easy access to any type of data in an array. It's Open Source so you can examine its implementation,混合使用宏和巧妙的函数。它具有许多功能,例如自动调整大小和垃圾收集。而且它非常成熟,经过充分测试。

GArray 会记住它的类型,因此没有必要一直告诉它。

    GArray *ints = g_array_new(FALSE, FALSE, sizeof(int));
    GArray *doubles = g_array_new(FALSE, FALSE, sizeof(double));

    int val1 = 23;
    double val2 = 42.23;

    g_array_append_val(ints, val1);
    g_array_append_val(doubles, val2);

底层纯 C 数组可以作为 data 字段 of the GArray struct 访问。它的类型为 gchar *,因此必须重铸。

    double *doubles_array = (double *)doubles->data;
    printf("%f", doubles_array[0]);

如果我们继续沿着您的路径前进,类型的不确定性会影响每个 "generic" 函数,您最终还是会编写并行实现。

例如,让我们编写一个将两个索引相加的函数。应该简单的东西。

首先,我们按照惯例来做。

int add_int(int *array, size_t idx1, size_t idx2) {
    return array[idx1] + array[idx2];
}

double add_double(double *array, size_t idx1, size_t idx2) {
    return array[idx1] + array[idx2];
}

int main() {
    int ints[] = {5, 10, 15, 20};

    int value = add_int(ints, 1, 2);

    printf("%d\n", value);
}

利用token concatenation,我们可以在它前面放一个聪明的宏来为我们选择正确的函数。

#define add(a, t, i1, i2) (add_ ## t(a, i1, i2))

int main() {
    int ints[] = {5, 10, 15, 20};

    int value = add(ints, int, 1, 2);

    printf("%d\n", value);
}

宏很聪明,但可能不值得额外的复杂性。只要您对命名保持一致,程序员就可以自己选择 _int_double 形式。但如果你愿意,它就在那里。


现在让我们用"one"函数来看一下。

// Using an enum gives us some type safety and code clarity.
enum Types { _int, _double };

void *add(void * array, enum Types type, size_t idx1, size_t idx2) {
    // Using an enum on a switch, with -Wswitch, will warn us if we miss a type.
    switch(type) {
        case _int : {
            int *sum = malloc(sizeof(int));
            *sum = (int *){array}[idx1] + (int *){array}[idx2];
            return sum;
        };
        case _double : {
            double *sum = malloc(sizeof(double));
            *sum = (double *){array}[idx1] + (double *){array}[idx2];
            return sum;
        };
    }; 
}

int main() {
    int ints[] = {5, 10, 15, 20};

    int value = *(int *)add((void *)ints, _int, 1, 2);

    printf("%d\n", value);
}

这里我们看到了感染。我们需要一个 return 值,但我们不知道类型,所以我们必须 return 一个空指针。这意味着我们需要分配正确类型的内存。我们需要访问具有正确类型、更多冗余、更多类型转换的数组。然后调用者必须搞乱一堆类型转换。

真是一团糟。

我们可以用宏清理一些冗余。

#define get_idx(a,t,i) ((t *){a}[i])
#define make_var(t) ((t *)malloc(sizeof(t)))

void *add(void * array, enum Types type, size_t idx1, size_t idx2) {
    switch(type) {
        case _int : {
            int *sum = make_var(int);
            *sum = get_idx(array, int, idx1) + get_idx(array, int, idx2);
            return sum;
        };
        case _double : {
            double *sum = make_var(double);
            *sum = get_idx(array, double, idx1) + get_idx(array, double, idx2);
            return sum;
        };
    }; 
}

您可能可以使用更多的宏来减少冗余,例如 ,但是天哪,这会迅速变成宏地狱。在某个时候,您不再使用 C 编写代码,因为您正在快速扩展使用宏堆栈实现的自定义语言。

在这里不起作用。为了真正对值做任何事情,我们需要知道它们的类型。


再一次,我无法足够强烈地表达 C 中的 tar 坑多态性有多大。