简体   繁体   English

将C泛型参数转换为函数指针

[英]C generic Parameter into Function pointer

Is it possible in C(not C++) to have a fuction pointer that takes a generic value(not a pointer), with -pedantic and -wall -werror flags set. 在C(不是C ++)中是否可以有一个带有通用值(不是指针)的功能指针,并设置了-pedantic和-wall -werror标志。

Note: I can't change the parameter Type. 注意:我无法更改参数类型。 The code has to support uint8_t, uint16_t, etc... types as the parameters 代码必须支持uint8_t,uint16_t等...类型作为参数

Goal: to solve the problem with code. 目标:用代码解决问题。

Question

Is there a way to typecast a uint8_t(and/or uint16_t) parameter to a void*(Approach1)? 有没有一种方法可以将uint8_t(和/或uint16_t)参数类型转换为void *(Approach1)? specifically to pass a non-pointer type value to a void* value. 特别是将非指针类型的值传递给void *值。

Is there a way to setup a Generic Type that will work with all the different values(Approach 2)? 有没有一种方法可以设置适用于所有不同值的通用类型(方法2)?

Last resort Is there a way to set a specific compiler Exception in the code?( this question has been answer ) 不得已时,是否可以在代码中设置特定的编译器异常?( 此问题已得到解答

Approach 1(causes a invalid conversion from uint8_t to void*) 方法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(causes a Too Many Parameters Compile Error) 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"
}

UPDATE 更新

To add clarity to the problem. 为了使问题更加清楚。 I have 100's of functions with 1 parameter. 我有100个带有1个参数的函数。 The 1 parameter of the functions are different types. 函数的1参数是不同的类型。 I can't change any of the functions, but I want to have 1 function pointer type(or more based on type) to any of the functions. 我无法更改任何函数,但我想为任何一个函数提供1个函数指针类型(或更多基于类型的指针)。 I can change any of the types associated with the function pointer and the type to the function pointer, but not what it is pointing too. 我可以更改与函数指针相关联的任何类型以及函数指针的类型,但不能更改其指向的类型。

No, it is not. 不它不是。

Simple answer: The called function does not know how to even fetch the argument. 简单答案:被调用函数甚至不知道如何获取参数。

Details: The function code is already fixed when it is called (executed). 详细信息:调用(执行)功能代码时,它已经固定。 So it contains code to access the argument, which depends on the type of the arguemtn (eg for an uint32_t, a 32 bit load/soter is required, for an uint8_t an 8 bit load/store). 因此,它包含用于访问参数的代码,该代码取决于struggmtn的类型(例如,对于uint32_t,需要32位加载/存储,对于uint8_t,则需要8位加载/存储)。 So it cannot handle even the value fetch properly. 因此,它甚至无法正确处理取值。 Different from C++ and higher languages like Python, C does not have a concept of run-time type identification built-in. 与C ++和Python等高级语言不同,C没有内置的运行时类型识别的概念。

However, you can pass a union to the function and handle each variant in the function seperately. 但是,您可以将union传递给函数,并分别处理函数中的每个变体。 That would generate all possible accesses. 这将生成所有可能的访问。 However, you have to specify which actual type is being passed. 但是,您必须指定要传递的实际类型。 This is normally done by a second argument which specifies the actual type . 这通常由指定实际类型的第二个参数完成。 That union could also be a struct composed of the type-identifier and the actual value. 该联合也可以是由类型标识符和实际值组成的结构。 But that is just an envelope, everything is still explicit. 但这只是一个信封,一切仍然是明确的。

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 });
}

The compound literal argument only works for constants, otherwise you have to assign the value to a union first (or just use that union). 复合文字参数仅适用于常量,否则必须首先将值分配给并集(或仅使用该并集)。 gcc has an extension to avoid this, so you can have a function which takes such a union, but the caller may use a simple cast, instead of a compound literal. gcc有一个扩展名可以避免这种情况,因此您可以有一个采用这种并集的函数,但是调用者可以使用简单的强制转换而不是复合文字。 That works for variables, too: 这也适用于变量:

    func(MY_VALUE_float, (MyValueType)1.0);

An alternative would be passing a const void * and internally casting. 一种替代方法是传递const void *并进行内部强制转换。 However, that is even more risky than the union approach. 但是,这比联合方法更具风险。

All approaches require pasing the actual type explicitly (eg using an enum). 所有方法都要求显式地粘贴实际类型(例如,使用枚举)。

C11 allows to create a macro which evaluates different expressions, according to the type of an argument using the new _Generic construct. C11允许使用新的_Generic构造根据自变量的类型创建一个评估不同表达式的宏。 With that the original approach can be simulated (using gcc extension, normal way is possible, but more complicated): 这样就可以模拟原始方法(使用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);

However, note the restriction of _Generic : No two compatible types are allowed for the type-selector (ie const int and int are not allowed both) for uint16_t and uint32_t this works, however. 但是,请注意_Generic的限制:uint16_t和uint32_t的类型选择器不允许两个兼容的类型(即const intint都不允许),这是可行的。

Note that gcc (you apparently use) supports C11 using -std=c11 or std=gnu11 . 请注意,gcc(您显然使用了)使用-std=c11std=gnu11支持C11。 The latter also enables GNU-extensions. 后者还启用GNU扩展。

Note that both of your exampels exibit Undefined Behaviour because you call a function through a pointer of another (function) type. 请注意,由于您通过另一种(函数)类型的指针调用函数,因此您的两个示例都具有未定义的行为。

I found a solution that relies on the fact that there are a limited number of function types known in advance. 我找到了一种解决方案,该解决方案依赖于事先已知的函数类型数量有限的事实。 I think however that it is too much hassle. 但是我认为这太麻烦了。 Just call the original function. 只需调用原始函数即可。

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;
}

Note that 注意

a function-pointer may be freely converted to any other function-pointer type and back again, and you will get the original pointer. 一个功能指针可以自由地转换为任何其他功能指针类型,然后再次返回,您将获得原始指针。

This is what we use. 这就是我们使用的。 We can't even use void * (because it is a data pointer, not a function pointer) to store the function pointer. 我们甚至不能使用void * (因为它是数据指针,而不是函数指针)来存储函数指针。 So I used void (*)(void) to store the function pointer. 所以我用void (*)(void)来存储函数指针。 An enum tells us to what kind of function we must convert it when we need to cal it. 一个枚举告诉我们在需要校准时必须转换哪种函数。

If you can use C11, there is a way to do this using _Generic : 如果可以使用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;
}

Seems to work well with gcc -std=c11 -pedantic -Wextra -Wall . 似乎可以与gcc -std=c11 -pedantic -Wextra -Wall

The short answer is no. 最简洁的答案是不。

You have several problems: 您有几个问题:

1) The different functions all have to have the same signature to allow the function pointer to point to them. 1)所有不同的函数都必须具有相同的签名,以允许函数指针指向它们。

2) The functions are taking their args by value which means a copy will be passed in and any actions you take on the value will not appear to have any affect outside the function call. 2)函数将它们的args值赋值,这意味着将传递一个副本,并且您对该值执行的任何操作似乎都不会在函数调用之外产生任何影响。 Since you don't allow pointers I cant see any way round this. 由于您不允许使用指针,因此我无法看到任何解决方法。

If you are not bothered about problem 2 then you could try declaring a variadic function which will accept args of any type. 如果您不关心问题2,则可以尝试声明可变参数函数,该函数将接受任何类型的args。

eg 例如

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);
}

Where MyType is an enum set up to identify which type is passed in. which is used like so: MyType是一个枚举,用于标识传入的类型。它的用法如下:

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

@bolov answer is good for handling the different types, this is just a different way of handling the same issue, but with 1 parameter. @bolov答案适用于处理不同类型,这只是处理同一问题的一种不同方式,但是具有1个参数。

The downside to this approach is that the type in main has to be GENERAL_TYPE. 这种方法的缺点是main中的类型必须为GENERAL_TYPE。 In my application I can change the type of the parameter, but I can change the type of the functions that I'm pointing to. 在我的应用程序中,我可以更改参数的类型,但是可以更改指向的函数的类型。

the (void(*)(GENERAL_TYPE))& handles the function's parameter types, and the Union handles the types of all the different sizes. (void(*)(GENERAL_TYPE))&处理函数的参数类型,而Union处理所有不同大小的类型。

Another option is to have function pointers for each type too. 另一种选择是也为每种类型都具有函数指针。

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM