2015-05-29 51 views
1

在C(而不是C++)中是否有可能使用一個泛型值(不是指針),設置了-pedantic和-wall -werror標誌的函數指針。C泛型參數轉換爲函數指針

注意:我無法更改參數類型。該代碼必須支持uint8_t,uint16_t等...類型作爲參數

目標:解決代碼問題。

問題

有沒有辦法來強制轉換一個uint8_t(和/或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" 
} 

UPDATE

爲了增加問題的清晰度。 我有100個具有1個參數的函數。 函數的1參數是不同的類型。 我不能更改任何函數,但我想要有1個函數指針類型(或更多基於類型)的任何函數。 我可以將任何與函數指針和類型關聯的類型更改爲函數指針,但不能指向它所指向的類型。

+0

「不是指針」 - 爲什麼不呢?這正是你如何去做的; '無效*'。 –

+1

只有在絕對需要的情況下,您才應繞過編譯器的類型檢查。現在大多數時候,有更好的方法。請記住:編譯器是您的自由人,您顯然也會因爲某種原因啓用警告。 – Olaf

+0

@Ed S.函數指針是指向數百個現有函數的指針,我不能更改這些函數的參數,但是我可以改變函數指針的結構 – Ashitakalax

回答

3

不,事實並非如此。

簡單的答案:被調用函數不知道如何獲取參數。

細節:功能代碼在調用(執行)時已經固定。所以它包含訪問參數的代碼,這取決於arguemtn的類型(例如,對於uint32_t,需要32位加載/ soter,對於uint8_t和8位加載/存儲)。所以它甚至無法正確處理值提取。 與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限制:沒有兩個兼容類型允許類型選擇器(即const intint是不允許的),但uint16_t和uint32_t可以。

請注意,gcc(您顯然使用)支持C11使用-std=c11std=gnu11。後者也支持GNU擴展。

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

注意

一個函數指針可以自由轉換成任何其他 函數指針類型,然後再返回,你會得到原始 指針。

這就是我們使用的。我們甚至不能使用void *(因爲它是一個數據指針,而不是一個函數指針)來存儲函數指針。所以我用void (*)(void)來存儲函數指針。一個枚舉告訴我們什麼樣的函數在我們需要調用時必須轉換它。

+0

我同意調用函數會更容易,特定的應用程序具有數百個具有簡單1參數值的函數。該接口消除了爲每個接口編寫代碼的需要。謝謝 – Ashitakalax

+0

忘記設置類型。請參閱更新 – bolov

+0

uint64_t不一定是最大的類型。對於整數(至少64位,但可能更大),實際上它會是'long long',但其他的可能會更大(例如向量類型,某些浮點類型會倍增)。對我來說,這太過於鑄造了。這是調試的噩夢。 – Olaf

0

簡短的回答是否定的。

你有幾個問題:

1)不同的功能都必須具有相同的簽名,讓函數指針指向他們。

2)函數按值取值,這意味着將傳入一個副本,並且對該值執行的任何操作在函數調用之外不會有任何影響。既然你不允許指針,我不能看到任何方式。

如果你不是問題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 
+0

如果不實際需要可變數量的參數,則不應使用變量參數。工會顯然是更好的方法。 – Olaf

1

如果你可以使用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很好地工作。

+0

這是一個很酷的技巧與_Generic – Ashitakalax

+0

@Olaf感謝評論,編輯。 –

0

@bolov答案適用於處理不同類型,這只是處理同一問題的一種不同方式,但帶有1個參數。

這種方法的缺點是main中的類型必須是GENERAL_TYPE。在我的應用程序中,我可以改變參數的類型,但是我可以改變我指向的函數的類型。

(void(*)(GENERAL_TYPE))&處理函數的參數類型,Union處理所有不同大小的類型。

另一種選擇是也爲每種類型都有函數指針。

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