我正在尝试使用c和函数指针。以下代码可以正常使用gcc作为编译器。
typedef int( * one_var_func)(int);
int Multiply(int x, int y) {
return x * y;
}
one_var_func curry(int( * f)(int, int), int x) {
int curried_f(int y) {
return f(x, y);
}
return (curried_f);
}
int apply(int( * f)(int), int x) {
return f(x);
}
int main() {
int( * p)(int, int);
one_var_func q;
int e;
p = & Multiply;
q = curry(p, 2);
e = apply( * q, 10);
printf("%d \n", e);
return 1;
}
然而,当我做这个小修改时;
int apply(int (*f)(int) ,int x){
int a;
a=f(x)
return a;
}
程序抛出分段错误。我不明白为什么以及如何。解释会非常好。
答案 0 :(得分:4)
嵌套函数是标准C中不存在的GCC扩展,因此这个答案(如问题)是特定于GCC的。
C中的嵌套函数不提供闭包。也就是说,嵌套函数只能访问外部函数的局部变量,直到外部函数返回为止。 GCC documentation对此主题有以下说法:
如果你试图在包含函数退出后通过其地址调用嵌套函数,那么所有的地狱都会破裂。如果您尝试在包含范围级别退出后调用它,并且如果它引用了一些不再在范围内的变量,您可能会很幸运,但冒风险并不明智。但是,如果嵌套函数没有引用超出范围的任何内容,那么您应该是安全的。
您的代码的两个版本都违反了此规则,那么为什么只有一个会导致段错误?一个答案是,就像"未定义的行为" "所有地狱都破裂了#34;可以描述所有类型的行为,包括看似按预期工作。
更多面向实现的答案是,从函数返回实际上并不会立即擦除其在堆栈上的内容 - 值只会保留在那里,直到另一个函数在需要堆栈空间时覆盖它们。引入一个新的局部变量使得该函数需要更多的堆栈空间,因此您的第二个函数会覆盖先前版本没有的堆栈内存。
答案 1 :(得分:2)
嵌套函数是gcc扩展名。 gcc文档声明,一旦包含函数调用退出,任何指向嵌套函数的指针都将变为无效,至少在它们尝试任何上级变量引用时。
这是有道理的,因为只要包含函数处于活动状态,其局部变量就会保持分配状态,并且可以解析嵌套函数的上级引用。但是一旦包含函数退出,就需要支持闭包以保留堆栈框架,但它并不是这样。
答案 2 :(得分:1)
C没有闭包的概念,这使得无法基于普通函数指针实现currying。你在这里尝试的是使用闭包:
one_var_func curry(int( * f)(int, int), int x) {
int curried_f(int y) {
return f(x, y);
}
return (curried_f);
}
这意味着嵌套函数“捕获”x
的值。但是在C中,任何具有自动存储持续时间的变量在执行离开封闭范围时都不再存在,并且没有可能阻止这种情况的闭包概念。
鉴于即使嵌套函数在C中也不存在,虽然GCC支持它作为扩展,但如果你真的需要应用currying,你必须定义自己的“函数对象”。 标准 C中的示例可能如下所示:
#include <stdio.h>
#include <stdlib.h>
typedef struct intfunc
{
int (*f)();
void *ctx;
} intfunc;
typedef struct curryctx
{
int (*f)();
int x;
} curryctx;
static int currycall(void *ctx, int x)
{
curryctx *cctx = ctx;
return cctx->f(cctx->x, x);
}
int intfunc_call(intfunc f, int x)
{
return f.ctx ? f.f(f.ctx, x) : f.f(x);
}
intfunc createfunc(int (*f)())
{
return (intfunc){f, 0};
}
intfunc curryfunc(int (*f)(), int x)
{
curryctx *cctx = malloc(sizeof *cctx);
if (!cctx) exit(1);
cctx->f = f;
cctx->x = x;
return (intfunc){currycall, cctx};
}
static int multiply(int x, int y)
{
return x*y;
}
int main()
{
intfunc multiply_by_two = curryfunc(multiply, 2);
printf("%d\n", intfunc_call(multiply_by_two, 10));
free(multiply_by_two.ctx);
return 0;
}
当然,这很复杂很快,所以我建议你最好完全忘记这个想法。
答案 3 :(得分:0)
当功能
one_var_func curry(int( * f)(int, int), int x)
{
int curried_f(int y)
{
return f(x, y); //f and x are local to curry
}
return (curried_f);
}
返回它的局部变量f和x不再在堆栈上。
当函数curried_f
时int curried_f(int y)
{
return f(x, y); //f and x are local to curry
}
被称为尝试访问x和f,这会导致分段错误。
答案 4 :(得分:0)
您声明了类型别名one_var_func
来代表a pointer to a function int->int
。您也可以将其声明为函数,不带指针,并使用指向此类型名称实例的指针。
首先,不允许使用类型别名定义函数。这是语法错误。
在函数定义中声明的标识符(函数的名称)应该是 具有函数类型,由函数定义的声明器部分指定。 138)
即。允许函数定义只显示:
function-definition:
declaration-specifiers declarator declaration-listopt compound-statement
事实上gcc只接受了警告,这是gcc扩展。
q = curry(p, 2);
你写的不是C,你知道一些函数式编程,并希望在C中应用它,但在C中,curry
这些相同的概念的应用方式不同,因为没有闭包的概念和本地绑定一个函数返回另一个函数并且包含第一个函数的本地函数不在C语言的内核中,你需要自己编码。
q
无法直接访问本地绑定2
。
e = apply( * q, 10);
同样,要评估q
上应用的10
,您需要一些应在q
内编码的本地环境。
关于你的代码还有其他的事情要说,在你尝试在CI中实现lisp评估器之前建议你先学习C,之后再从here开始阅读lisp的实现,因为它们是不言而喻。