2011-04-02 49 views
10

我正在編寫一個C程序,如果已經是該語言的一部分,它將受益於部分函數應用。我需要提供一個帶有固定簽名的函數指針,但有時我需要調用已經寫好的需要參數的函數。仿真c中的部分函數應用

我可以寫很多的小功能,通話對方爲:

// example, not actual code 
void (*atJoystickLeft)(void); // fixed signature 

.. code .. 
atJoystickLeft = &func1; 
.. code .. 

void func1(void) { func2(10, "a string"); }/

我vould喜歡,而不是:

atJoystickLeft = &func2(10, "a string"); 

,所以我可以處置功能FUNC1,這只是設置的調用func2的參數。

這會使代碼至少縮小20% - 30%。我的簡單問題是:是否有人有模仿C中部分函數應用的技巧? (C99)

+0

我不明白部分函數應用程序在哪裏? 'func2'的代碼是什麼? – pajton 2011-04-02 16:23:57

+3

如果我認爲他是對的,他想要咖喱。如果這是你正在尋找看到http://stackoverflow.com/questions/1023261/is-there-a-way-to-do-currying-in-c – flolo 2011-04-02 16:39:43

+0

簡短的回答是,不,C不支持它。你可能會一起攻擊一個帶有函數地址的可變前端,並創建一個堆棧框架以用這些參數調用該函數,但它不會真的是可移植的,並且您將失去對任何函數的類型檢查這樣調用。海事組織,類型檢查的損失使其成爲淨虧損。 – 2011-04-02 16:51:44

回答

8

簡單的答案是沒有,C沒有按不支持它。

你可以將一個可變前端拿到一個函數的地址,並創建一個堆棧框架以用這些參數調用該函數,但它不會是便攜式的。如果你想讓它變得便攜,可以自由修改你要通過這個前端調用的其他函數(轉換成不適合直接調用的表單),你可以重寫所有這些函數來接收va_alist作爲他們的唯一參數,並使用va_arg檢索正確的數量/類型的參數:

// pointer to function taking a va_alist and return an int: 
typedef int (*func)(va_alist); 

void invoke(func, ...) { 
    va_alist args; 
    va_start(args, func); 
    func(args); 
    va_end(args); 
} 

編輯:對不起,因爲@missingno指出,我沒有做這項工作相當它的目的的方式。這實際上應該是兩個功能:一個接受輸入並將它們包裝到一個結構中,另一個接受結構並調用預期的功能。

struct saved_invocation { 
    func f; 
    argument_list args; 
}; 

saved_invocation save(func f, ...) { 
    saved_invocation s; 
    va_alist args; 
    s.f = f; 
    va_start(args, f); 
    va_make_copy(s.args, args); 
    va_end(args); 
} 

int invoke(saved_invocation const *s) { 
    s->f(s->args); 
} 

對於va_make_copy,你會遇到更多非標準的東西。它不會與va_copy - va_copy通常只存儲一個指向參數開頭的指針,它只在當前函數返回前保持有效。對於va_make_copy,您必須存儲所有實際參數,以便稍後檢索它們。假設你使用了argument結構,我將在下面概述一下,你可以通過使用va_arg來查看參數,並使用malloc(或其他)來爲每個參數分配一個節點,並創建某種動態數據結構。,鏈表,動態數組)來保存參數,直到您準備好使用它們。

您還必須添加一些代碼來處理釋放內存,一旦完成特定的綁定函數。它也會實際上改變你的函數簽名,直接取得一個va_list,並採取你設計的任何類型的數據結構來保存你的參數列表。

[編輯完]

這將意味着你要調用所有其他函數簽名將需要:

int function(va_alist args); 

...然後每個功能必須通過va_arg檢索它的參數,所以(例如),這是要帶兩個整數作爲參數,並返回它們的和看起來像這樣的功能:

int adder(va_alist args) { 
    int arg1 = va_arg(args, int); 
    int arg2 = va_arg(args, int); 

    return arg1 + arg2; 
} 

這有兩個明顯的問題:第一,即使我們不再需要爲每個函數分別包裝,我們仍然最終爲每個函數添加額外的代碼,以便通過單個包裝器調用它。在代碼大小方面,它不可能比盈虧更好,並且可能很容易成爲淨虧損。

然而,更糟糕的是,由於我們現在將所有函數的所有參數作爲變量參數列表檢索,因此我們不再對參數進行任何類型檢查。如果我們想厲害的是,它會(當然)有可能增加一個小包裝類型和代碼來處理,以及:

struct argument { 
    enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ }; 
    union data { 
     char char_data; 
     short short_data; 
     int int_data; 
     long long_data; 
     /* ... */ 
    } 
} 

然後,當然,你會寫更代碼查詢每個參數的枚舉表明它是預期的類型,並且如果是的話從聯合中檢索正確的數據。然而,這會增加一些嚴重的醜陋調用的功能 - 而不是:

invoke(func, arg1, arg2); 

...你會得到這樣的:

invoke(func, make_int_arg(arg1), make_long_arg(arg2)); 

顯然,這可以完成。不幸的是,它仍然沒有好處 - 減少代碼的最初目標幾乎肯定已經完全喪失。這確實消除了包裝函數 - 但是我們不是使用簡單的包裝和簡單調用的簡單函數,而是使用複雜的函數和複雜的調用,以及一小段額外的代碼來將值轉換爲我們的特殊參數類型,另一個從參數中檢索值。

雖然有些案例可以證明這樣的代碼是正確的(例如,爲Lisp寫一些類似的解釋器),但我認爲在這種情況下,它可能會導致淨虧損。即使我們忽略額外的代碼來添加/使用類型檢查,每個函數中的代碼都會檢索參數,而不是直接接收它們,而不是我們試圖替換的包裝器。


  1. 「將無法移植」幾乎是輕描淡寫 - 你真的不能這樣 c執行的話 - 你只是需要使用匯編語言,甚至上手。
+0

+1爲C黑魔法。但我在這裏沒有看到如何使用它來模擬部分函數應用程序(您仍然需要每次都手動創建一個閉包,對吧?) – hugomg 2011-04-03 17:48:00

+0

@missingno:對不起,我有點遺憾。 – 2011-04-03 18:06:31

+0

謝謝。你真的充實了爲什麼這不是一個好主意,至少對我的分配來說。 :-) +1 – Gorgen 2011-04-04 08:50:24

2

我不知道任何直接的方式做你想做的事情,因爲C不支持閉包或者其他任何方式來將「隱藏參數」傳遞給你的函數(不使用全局變量或靜態變量,strtok風格)

你似乎來自FP背景。在C中,所有事情都必須手工完成,而且我會冒險說,在這種情況下,嘗試用OO風格來模擬事物可能會更好:通過讓您的函數變爲「方法」,讓它們接收額外的參數,指向「內部屬性」結構的指針,即thisself。 (如果你碰巧知道Python這應該是很清楚:))

在這種情況下,例如,func1可能會變成這樣:

typedef struct { 
    int code; 
    char * name; 
} Joystick; 

Joystick* makeJoystick(int code, char* name){ 
    //The constructor 
    Joystick* j = malloc(sizeof Joystick); 
    j->code = code; 
    j->name = name; 
    return j; 
} 

void freeJoystick(Joystick* self){ 
    //Ever noticed how most languages with closures/objects 
    // have garbage collection? :) 
} 

void func1(Joystick* self){ 
    func2(self->code, self->name); 
} 

int main(){ 
    Joystick* joyleft = make_Joystick(10, "a string"); 
    Joystick* joyright = make_Joystick(17, "b string"); 

    func1(joyleft); 
    func1(joyright); 

    freeJoystick(joyleft); 
    freeJoystick(joyright); 

    return 0; 
} 
+0

我想你的意思是'func2(joyright)'是'func1(joyright)' – 2011-04-03 06:50:46

+0

@Null設置:當然:) – hugomg 2011-04-03 13:44:32