假设我想用普通的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中是否存在一种方法?
答案 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
,这没有任何帮助,因为你只是放弃了你输入的内容。
获取新函数指针的唯一另一种方法是将指向对象类型的指针转换为函数指针,并且它不可移植,因为它不在指针转换列表中你可以做(但是,它被注意为一个共同的扩展名)。
有一段时间我以为你可能会找到setjmp
和longjmp
的某个地方,但这只是替换了存储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。