使用ISO C99进行卷曲/装订

时间:2014-05-24 20:27:25

标签: c currying

假设我想用普通的C实现数值积分例程。看起来像这样:

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

我经常发现实际上依赖于多个变量的函数,我想要整合第一个函数。说我想整合这个:

double func(double x, double z);

关于x。我无法将func传递给integrate,因为它的签名错误。现在我知道了以下的解决方法,当我们参加数字课程时,我们采用了这些解决方法:

  • 使用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);
    }
    

    并将inner传递给integrate函数。这是非标准的,感觉不太好。

  • 使用全局变量

    z的值可以存储在全局变量中。这将要求函数func可编辑以使用全局变量中的z而不是参数。这可能是不可能的。然后它也破坏了并发性,一般情况下都很糟糕。

在没有破坏某些东西的情况下,在普通C中是否存在一种方法?

5 个答案:

答案 0 :(得分:4)

此问题的一个常见解决方案是将设计更改为:

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

此处您将其他void *传递给integrate,并将其传递回f。这可以用于传递任意数据,在您的情况下,您将传递指针z,并在函数f内,您将指针转回其原始类型并恢复值{{ 1}}。这种模式在C库中无处不在,例如这里是pthread_create的原型:

z

答案 1 :(得分:3)

不,C无法在C函数/ C函数指针级别执行此操作。为特定应用程序(如数学函数和集成)执行此操作的最佳方法是使用您自己的结构来表示数学函数,以及已绑定的任何变量槽。我可能不会使函数采用不同数量的参数,而是指向一组参数的指针;这使得以不同方式以编程方式调用它们变得容易得多。

或者你可以使用libffi之类的东西来做这种绑定/闭包,但它绝对不是便携式或高效的。

答案 2 :(得分:2)

您的问题也可以通过变量参数函数来解决(并且稍加努力):

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

虽然不确定我是否应该以任何方式考虑清洁......

答案 3 :(得分:0)

我99%确定使用纯C89是不可能的。为此,您必须在运行时创建一个新的函数指针。有两种方法可以获取函数指针:来自函数表达式,或来自返回函数指针的标准库函数。函数表达式是指在编译时定义的函数,因此不会起作用。返回函数指针的唯一标准库函数是signal,这没有任何帮助,因为你只是放弃了你输入的内容。

获取新函数指针的唯一另一种方法是将指向对象类型的指针转​​换为函数指针,并且它不可移植,因为它不在指针转换列表中你可以做(​​但是,它被注意为一个共同的扩展名)。

有一段时间我以为你可能会找到setjmplongjmp的某个地方,但这只是替换了存储double的问题,即存储{ {1}}。

我确实得到了今天在我的系统上运行的东西,但由于它不可移植甚至升级我的编译器可能会破坏它。一般的想法是创建一个结构,其中包含指向原始函数的指针jmp_buf,以及一些机器代码来访问该信息并调用原始函数。我不建议你使用它,但我发现它很有趣。我在评论中指出了一些不可移植的假设。

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版本,那么您可能会在很多地方工作。但是我确定有些计算机可以运行在你无法做到的地方。

答案 4 :(得分:0)

我认为使用GCC扩展功能 中的功能是一个可行的解决方案。顺便说一下,它不是GCC扩展的一个例子,而只是一个辅助函数。

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

这完全有效的C89。