2014-05-24 33 views
3

說我要實現與普通C的數值積分程序。該會是這個樣子:柯里/符合ISO C99結合

double integrate(double (*f)(double), double lower, double upper, double step)); 

我經常發現,實際上依賴於多個變量的功能,我想整合第一個。假設我想整合:

double func(double x, double z); 

關於x。由於簽名錯誤,我無法將func傳遞到integrate。現在我知道了以下變通方法,這是由我們,當我們拿着NUMERICS過程中使用:

  • 使用C++

    我已經使用C++和IST std::bind創建一個仿函數(函數對象)我可以傳遞到整合例程。現在我只是使用lambda函數來完成它。

  • 使用GCC擴展功能功能

    隨着GCC,你可以在一個函數聲明功能。因此,人們可以做

    // z is set to some value in this function scope here. 
    double inner(double x) { 
        return func(x, z); 
    } 
    

    並傳遞innerintegrate功能。這是不規範的,感覺不太好。

  • 使用全局變量

    z值可以被存儲在一個全局變量。這將要求函數func可編輯爲使用全局變量z而不是參數。這可能是不可能的。那麼它也會打破併發性,總的來說只是不好的。

在沒有破壞的情況下,在純C中存在一種方法嗎?

+2

沒有ISO C98。最初的標準是ANSI C89;它作爲ISO C90被採用,沒有顯着變化。後來的標準是ISO C99和ISO C11。 –

+0

任何事情,你可以做一個全球性的,你可以做到沒有全球... –

+0

@KeithThompson是的,我的意思是C99。我寫這個時可能考慮過C++ 98。 –

回答

3

不,C無法在C函數/ C函數指針級別執行此操作。爲特定應用程序(如數學函數和集成)執行此操作的最佳方法是使用您自己的結構來表示數學函數以及已綁定的任何可變插槽。我可能不會讓這些函數帶有不同數量的參數,而是一個指向一組參數的指針;這使得以不同的方式以編程方式調用它們變得更容易。

另外,你可以使用類似libffi這樣的綁定/封閉類型,但它絕對不是便攜式或高效的。這個問題

3

一個共同的解決辦法是改變設計成這樣:

double integrate(double (*f)(double, void*), void*, 
         double lower, double upper, double step); 

在這裏你傳遞一個附加void *integrate,並且這是被傳遞迴f。這可以用來傳遞任意數據,在你的情況下,你會傳遞一個指針到z,並且在函數f內,你會將指針轉換回原來的類型並且恢復值z。這種模式是C庫中無處不在,例如這裏是pthread_create原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
         void *(*start_routine) (void *), void *arg); 
0

我99%肯定它是不可能用純C89。爲了做到這一點,你必須在運行時創建一個新的函數指針。有兩種獲取函數指針的方法:從函數表達式或從返回函數指針的標準庫函數中獲取函數指針。函數表達式是指在編譯時定義的函數,所以不起作用。它返回一個函數指針的唯一標準庫函數是signal,這是沒有任何幫助,因爲你只把它弄出來你把豆。

唯一的其他方式來獲得一個新的函數指針將指向轉換爲一個對象類型轉換爲一個函數指針,並且這不是可移植的,因爲它不在可以執行的指針轉換列表中(不過,它被記錄爲一個通用擴展)。

一小會兒我想你也許能setjmplongjmp得到的地方,但只是替換存儲jmp_buf的問題存儲double的問題。

我確實得到了今天在我的系統上正常工作的事情,但由於它不可移植,即使升級我的編譯器也可能會破壞它。總體思路是創建一個結構,其中包含指向原始函數的指針double z以及一些訪問該信息並調用原始代碼的機器代碼。我不建議你使用這個,但我覺得它很有趣。我已經在評論中指出了一些不可移植的假設。

/* platform-specific include */ 
#include <sys/mman.h> 
/* standard includes */ 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

double func(double x, double z) 
{ 
    printf("%g\n", z); 
    return x; 
} 

double bar(double x) 
{ 
    printf("y\n"); 
    return x; 
} 

double call(double (*f)(double)) 
{ 
    return f(0.0); 
} 

struct cDblDblRetDbl 
{ 
    double (*function)(double, double); 
    double a; 
    char code[1]; 
    double pad; 
}; 

static double helper(double x) 
{ 
    /* Casting a function pointer to an object pointer is 
    * not provided by the standard. 
    * In addition, this only works because the compiler 
    * happens to use RIP-relative addressing, so "helper" 
    * points to the beginning of the currently executing 
    * function, which is actually a copy of the one in 
    * the object file. 
    * It's worth noting explicitly that nothing in the 
    * C standard says that a pointer to a function will 
    * point to its machine code. 
    */ 
    double *dp = (double *) helper; 
    struct cDblDblRetDbl *data; 
    /* Modify it to point after the structure. 
    * This depends on the alignment and padding of the 
    * structure, which is not portable. 
    */ 
    dp += 2; 
    data = (struct cDblDblRetDbl *) dp; 
    /* back it up to the structure */ 
    --data; 
    /* now call the function with the saved data. */ 
    return data->function(x, data->a); 
} 

/* There is no way to get the code size of a function, 
* so this is very nonportable. 
* I found it by examining the object file. 
*/ 
#define CODE_SIZE 0x60 

double (*curryDoubleDoubleReturningDouble(double (*function)(double, double), double a))(double) 
{ 
    size_t size = sizeof(struct cDblDblRetDbl) + CODE_SIZE; 
    /* valloc is nonstandard but we need an area aligned to a 
    * page boundary for mprotect. 
    */ 
    struct cDblDblRetDbl *result = valloc(size); 
    result->function = function; 
    result->a = a; 
    /* Copy the code of the helper function into the structure. 
    * Once again, we're casting a function pointer to an 
    * object pointer and the standard doesn't say you can do that. 
    */ 
    memcpy(result->code, (void *) helper, CODE_SIZE); 
    /* Memory protection is out of the scope of the standard, 
    * and in a real program we need to check the return value. 
    */ 
    mprotect(result, CODE_SIZE, PROT_READ | PROT_EXEC | PROT_WRITE); 
    /* Casting an object pointer to a function pointer is also 
    * not provided by the standard. 
    * This example leaks memory. 
    */ 
    return (double(*)(double)) result->code; 
} 

int main() 
{ 
    call(bar); 
    call(curryDoubleDoubleReturningDouble(func, 5)); 
    call(curryDoubleDoubleReturningDouble(func, 7)); 
    call(curryDoubleDoubleReturningDouble(func, 42.9)); 
} 

如果你寫了裝配helper,創造了curryDoubleDoubleReturningDouble操作系統特定版本中,你很可能得到這個工作了很多地方。但是我確信有一些電腦C運行在你無法做到的地方。

+1

將一種類型的函數指針轉換爲任何其他類型的函數指針並返回是合法的,只要您不嘗試從轉換後的類型中調用它即可,因此您可以使用任何類型的函數指針作爲一種'void *'。然而,在調用它之前,你必須將它轉換成正確的類型,所以你必須在某處存儲信息來做到這一點(也許通過另一個參數來實現這個目的),所以這不是一個完美的解決方案。 –

+0

'jmpbuf'被定義爲一個數組類型,所以你不能用賦值來移動它的值。但你*可以''memcpy'它。 –

+0

@PaulGriffiths,這是真的,但它不會給你一個新的函數指針,我不認爲。 –

0

我想你的例子在使用GCC擴展函數功能是一個可行的解決方案。順便說一下,它不是GCC擴展的例子,而只是一個輔助函數。

double inner(double x) { 
    return func(x, 100); 
} 

這是完全有效的C89。

+0

當然,缺點是你必須在你的源代碼中爲'z'的每個值創建一個單獨的'inner'版本。 –

+0

當然,但我想爲許多不同的'z'生成一個新函數。我相應地更新了這個問題。 –

1

您的問題,也可以用可變參數的函數(及略微更努力)解決:

#include <stdarg.h> 

double integrate(double (*f)(double, ...), double lower, double upper, double step) 
{ 
    return (f)(lower, upper); 
} 

double func1(double x, ...) 
{ 
    va_list ap; 
    double ret; 

    va_start(ap, x); 

    ret = x * va_arg(ap, double); 

    va_end(ap); 

    return ret; 
} 

double func2(double x, ...) 
{ 
    return x; 
} 

雖然不知道我是否應該考慮以任何方式清潔...