如何构造一个带有 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;
}
上面的代码有两个问题:正确输入的 valueT
和 arrayT
在条件块中的范围内,并且对代码的主要部分不可见。将他们的声明移出块在给定的代码结构中是不可行的,因为他们需要不同的名称 int
和 double
版本,打败了我的整个想法试图实现。另一个问题是 valueT
和 arrayT
是函数局部的。我真正想要的是设置参数array
:array[0] = *value
.
看来我想做的事情在 C 中是不可能的...有没有办法可以做到这一点?
编辑:
数组行的赋值是为了演示我想做什么,那部分有很多代码。除了 int
和 double
之外,还会有许多其他类型。将分配行移动到块中将意味着太多代码重复。
您问题的直接答案是在指针有效的块中取消引用赋值:
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。沿着这条路走下去就是疯狂、无法维护的代码和新的编程语言。
相反,我强烈建议重构您的代码以使用更好的方法处理混合数据。 union
或 struct
或指针或 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 坑多态性有多大。
我正在尝试创建一个函数,其中参数作为 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;
}
上面的代码有两个问题:正确输入的 valueT
和 arrayT
在条件块中的范围内,并且对代码的主要部分不可见。将他们的声明移出块在给定的代码结构中是不可行的,因为他们需要不同的名称 int
和 double
版本,打败了我的整个想法试图实现。另一个问题是 valueT
和 arrayT
是函数局部的。我真正想要的是设置参数array
:array[0] = *value
.
看来我想做的事情在 C 中是不可能的...有没有办法可以做到这一点?
编辑:
数组行的赋值是为了演示我想做什么,那部分有很多代码。除了 int
和 double
之外,还会有许多其他类型。将分配行移动到块中将意味着太多代码重复。
您问题的直接答案是在指针有效的块中取消引用赋值:
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。沿着这条路走下去就是疯狂、无法维护的代码和新的编程语言。
相反,我强烈建议重构您的代码以使用更好的方法处理混合数据。 union
或 struct
或指针或 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 中的 tar 坑多态性有多大。