C泛型参数转化为函数指针

C generic Parameter into Function pointer

在 C(不是 C++)中是否可以有一个函数指针,它采用通用值(不是指针),并设置了 -pedantic 和 -wall -werror 标志。

注意:我无法更改参数类型。代码必须支持 uint8_t、uint16_t 等...类型作为参数

目标:用代码解决问题

问题

有没有办法将 uint8_t(and/or uint16_t) 参数强制转换为 void*(Approach1)? 专门用于将非指针类型值传递给 void* 值。

有没有一种方法可以设置适用于所有不同值的通用类型(方法 2)?

不得已 有没有办法在代码中设置特定的编译器异常?(this question has been answer)

方法 1(导致从 uint8_t 到 void* 的无效转换)

typedef struct
{
    void (*set_func)(void*);
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    uint8_t a = 123;
    uint16_t b = 321;
    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue to appease compiler warning
    pointerFunction.set_func = (void(*)(void*))&setValue;
    pointerFuncionShort.set_func = (void(*)(void*))&setShortValue;


    //use the function pointer with non-pointer parameter
    // Compile ERROR thrown invalid conversion from uint8_t to void*
    pointerFunction.set_func(a);
    pointerFuncionShort.set_func(b);
}

Aprroach 2(参数过多编译错误)

 typedef struct
{
    void (*set_func)();//Blank parameter to allow multiple args
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    uint8_t a = 123;
    uint16_t b = 321;

    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue to appease compiler warning
    pointerFunction.set_func = (void(*)())&setValue;
    pointerFuncionShort.set_func = (void(*)())&setShortValue;

    //use the function pointer with non-pointer parameter
    pointerFunction.set_func(a);// Compile ERROR thrown "Too many Args"
    pointerFuncionShort.set_func(b);// Compile ERROR thrown "Too many Args"
}

更新

使问题更加清晰。 我有 100 个带有 1 个参数的函数。 函数的1个参数是不同的类型。 我不能更改任何函数,但我希望有 1 个函数指针类型(或更多基于类型)到任何函数。 我可以更改与函数指针关联的任何类型和函数指针的类型,但也不能更改它指向的内容。

请注意,您的两个示例都存在未定义行为,因为您通过另一种(函数)类型的指针调用函数。

我找到了一个解决方案,该解决方案依赖于预先知道的函数类型数量有限这一事实。但是我认为这太麻烦了。调用原函数即可。

enum GFType {
  GF_UINT8,
  GF_UINT16 // etc
};

struct GenericFunction {
  void (*func)(void);
  GFType type;
};

void callGenericFunction(GenericFunction func, uint64_t p) // largest type
{
  switch (func.type) {
  case GF_UINT8:
    ((void (*)(uint8_t))func.func)(p);
    return;
  case GF_UINT16:
    ((void (*)(uint16_t))func.func)(p);
    return;
  default:
    assert(1); // unimplemented function type
  }
}

void setValue(uint8_t byteValue) // Not a pointer parameter
{
  byteValue++;
}

void setShortValue(uint16_t byteValue) // Not a pointer parameter
{
  byteValue++;
}

int main() {
  uint8_t a = 123;
  uint16_t b = 321;

  GenericFunction pointerFunction;
  GenericFunction pointerFunctionShort;

  pointerFunction.func = (void (*)(void))setValue;
  pointerFunction.type = GF_UINT8;

  pointerFunctionShort.func = (void (*)(void))setShortValue;
  pointerFunction.type = GF_UINT16;


  callGenericFunction(pointerFunction, a);
  callGenericFunction(pointerFunctionShort, b);

  return 1;
}

注意

a function-pointer may be freely converted to any other function-pointer type and back again, and you will get the original pointer.

这就是我们使用的。我们甚至不能用void *(因为是数据指针,不是函数指针)来存放函数指针。所以我用void (*)(void)来存放函数指针。一个枚举告诉我们当我们需要调用它时我们必须将它转换成什么样的函数。

简短的回答是否定的。

你有几个问题:

1) 不同的函数都必须具有相同的签名以允许函数指针指向它们。

2) 这些函数按值获取它们的 args,这意味着将传入一个副本,并且您对该值执行的任何操作似乎都不会在函数调用之外产生任何影响。因为你不允许指针,所以我看不出有什么办法解决这个问题。

如果您不为问题 2 所困扰,那么您可以尝试声明一个接受任何类型参数的可变参数函数。

例如

void doSomethingWithValue(enum MyType type ...)
{
    va_list args;
    va_start( args, type);

    switch( type)
    {
        case Uint8Type:
        {
             uint8_t value = va_arg(args, uint8_t);

             //doSomething to value
        }
        break;
        .
        .
        .
    }

    va_end(args);
}

其中 MyType 是一个枚举设置,用于标识传入的类型。 像这样使用:

 uint8_t value = 7;
 doSomethingWithValue(Uint8Type, value);
 //note that value is still 7

不,不是。

简单的回答:被调用的函数甚至不知道如何获取参数。

说明:函数代码在调用(执行)时已经固定。因此它包含访问参数的代码,这取决于 arguemtn 的类型(例如,对于 uint32_t,需要 32 位 load/soter,对于 uint8_t,需要 8 位 load/store).因此它甚至无法正确处理取值。 与 C++ 和 Python 等高级语言不同,C 没有内置 运行 时间类型识别的概念。

但是,您可以将 union 传递给函数并分别处理函数中的每个变体。这将生成所有可能的访问。但是,您必须指定要传递的实际类型。这通常由指定 实际类型 的第二个参数完成。 该联合也可以是由类型标识符和实际值组成的结构。但这只是一个信封,一切都还很明确。

typedef union {
    int i;
    float f;
} MyValue;

typedef enum {
    MY_VALUE_int,
    MY_VALUE_float
} MyValueType;

void func(MyValueType type, MyValue value)
{
    switch ( type ) {
        ...
    }
}

int main(void)
{
    func(MY_VALUE_int, (MyValueType){ .i=1 });
}

复合文字 参数仅适用于常量,否则您必须先将值分配给联合(或仅使用该联合)。 gcc 有一个扩展来避免这种情况,所以你可以有一个接受这样一个联合的函数,但是调用者可以使用一个简单的转换,而不是一个复合文字。这也适用于变量:

    func(MY_VALUE_float, (MyValueType)1.0);

另一种方法是传递 const void * 并进行内部转换。然而,这比联合方法的风险更大。

所有方法都需要显式传递实际类型(例如使用枚举)。

C11 允许创建一个宏来计算不同的表达式,根据使用新的 _Generic 结构的参数类型。这样就可以模拟原来的方法(使用gcc扩展,正常的方法是可以的,但更复杂):

// use the code of the first block here

#define func_generic(val) _Generic((val), \
    int : func(MY_VALUE_int, (MyValueType)(val)), \
    int : func(MY_VALUE_int, (MyValueType)(val)) )

// and call like:
func_generic(1);
func_generic(1.0);

但是,请注意 _Generic 的限制:对于 [=49],类型选择器不允许有两个兼容的类型(即 const intint 都不允许) =] 和 uint32_t 这有效,但是。

请注意,gcc(您显然使用)支持使用 -std=c11std=gnu11 的 C11。后者还启用 GNU 扩展。

如果你可以使用 C11,有一种方法可以使用 _Generic:

#include <stdio.h>
#include <inttypes.h>

#define setvalue_generic(x) _Generic((x), \
    uint8_t: setValue, \
    uint16_t: setShortValue \
    )(x)

void setValue(uint8_t byteValue)
{
    printf("setValue: %" PRIu8 "\n", byteValue);
    byteValue++;
}

void setShortValue(uint16_t byteValue)
{
    printf("setValue: %" PRIu16 "\n", byteValue);
    byteValue++;
}

int main(void)
{
    uint8_t a = 123;
    uint16_t b = 321;
    setvalue_generic(a);
    setvalue_generic(b);

    return 0;
}

似乎适用于 gcc -std=c11 -pedantic -Wextra -Wall

@bolov 的回答适用于处理不同的类型,这只是处理同一问题的不同方式,但有 1 个参数。

这种方法的缺点是 main 中的类型必须是 GENERAL_TYPE。在我的应用程序中,我可以更改参数的类型,但我可以更改我指向的函数的类型。

(void(*)(GENERAL_TYPE))& 处理函数的参数类型,联合处理所有不同大小的类型。

另一种选择是也为每种类型提供函数指针。

typedef union generalType
{
    uint8_t byteData;
    uint16_t shortData;
    uint32_t intData;
    int     integerData;
    uint64_t longData;
    void *  voidData;
    //Add any type
} GENERAL_TYPE;

typedef struct
{
    void (*set_func)(GENERAL_TYPE);
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    GENERAL_TYPE a.byteData = 123;//restricted to use GENERAL_TYPE here
    GENERAL_TYPE b.shortData = 321;
    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue parameter to be a general type will 
    //Allow it to send the data of whatever type.
    pointerFunction.set_func = (void(*)(GENERAL_TYPE))&setValue;
    pointerFuncionShort.set_func = (void(*)(GENERAL_TYPE))&setShortValue;

    //use the function pointer with non-pointer parameter
    pointerFunction.set_func(a);
    pointerFuncionShort.set_func(b);
}