【问题标题】:Function Pointers in C and their behaviourC 中的函数指针及其行为
【发布时间】:2024-04-22 04:00:02
【问题描述】:

我正在试验 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;
}

程序引发分段错误。我不明白为什么以及如何。一个解释会非常好。

【问题讨论】:

  • 我不完全明白你在这里想要做什么,但你的代码中有一个 嵌套函数,这是 C 中不存在的。
  • @FelixPalmen 这是一个 GCC 扩展。不知道它是否真的可以做成咖喱,这似乎很神奇。
  • 段错误在哪里?你的调试器告诉你什么?
  • 您在a=f(x) 之后错过了;,这在这里工作正常:rextester.com/IAKVTA93918
  • @unwind 好吧,如果允许返回指向嵌套函数的指针,支持闭包是有意义的......

标签: c gcc segmentation-fault


【解决方案1】:

嵌套函数是标准 C 中不存在的 GCC 扩展,因此这个答案(就像问题一样)是 GCC 特定的。

C 中的嵌套函数不提供闭包。也就是说,嵌套函数只能访问外部函数的局部变量,直到外部函数返回。 GCC documentation 对这个问题有以下看法:

如果您在包含函数退出后尝试通过其地址调用嵌套函数,那么一切都会崩溃。如果您尝试在包含范围级别退出后调用它,并且如果它引用了一些不再在范围内的变量,您可能会很幸运,但冒险并不明智。但是,如果嵌套函数没有引用超出范围的任何内容,那么您应该是安全的。

您的两个版本的代码都违反了这条规则,那么为什么只有一个版本会导致段错误?一个答案是,就像“未定义的行为”一样,“所有的地狱都崩溃了”可以描述所有类型的行为,包括看似按预期工作。

更面向实现的答案是,从函数返回实际上并不会立即删除堆栈上的内容——这些值只是保留在那里,直到另一个函数在需要堆栈空间时覆盖它们。引入新的局部变量会使函数需要更多的堆栈空间,因此您的第二个函数会覆盖以前版本没有的堆栈内存。

【讨论】:

    【解决方案2】:

    嵌套函数是 gcc 扩展。 gcc 文档指出,一旦包含函数调用退出,任何指向嵌套函数的指针都会变得无效,至少在它们尝试任何上层变量引用时是这样。

    这是有道理的,因为只要包含函数处于活动状态,它的局部变量就会保持分配状态,并且可以解析来自嵌套函数的上层引用。但是一旦包含函数退出,它就需要支持闭包来保存堆栈帧,而它没有。

    【讨论】:

      【解决方案3】:

      C 没有 closures 的概念,这使得基于普通函数指针实现柯里化是不可能的。您在这里尝试的是使用 closure

      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 支持它作为扩展,但如果你真的需要应用柯里化,你必须定义自己的“函数对象”。 standard 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;
      }
      

      当然,这很快就会变得复杂,所以我建议你最好完全忘记这个想法。

      【讨论】:

        【解决方案4】:

        您声明了类型别名 one_var_func 来表示 a pointer to a function int-&gt;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);
        

        同样,要评估应用于10q,您需要一些应该在q 中编码的本地环境。

        关于你的代码还有其他要说的,在你尝试在 C 中实现 lisp 的评估器之前,我建议你先学习 C,然后从 here 开始阅读 lisp 的实现,因为它们不是 self很明显。

        【讨论】:

          【解决方案5】:

          当函数

          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,导致分段错误。

          【讨论】:

            最近更新 更多