c - 这是什么疯狂?

标签 c gcc

我从来没有见过这样的事情;我似乎无法理解它。这段代码到底做了什么?它看起来 super 花哨,而且我很确定我的 C 书中的任何地方都没有描述这些东西。 :(

union u;
typedef union u (*funcptr)();

union u {
  funcptr f;
  int i;
};

typedef union u $;

int main() {
  int printf(const char *, ...);

  $ fact =
      ($){.f = ({
            $ lambda($ n) {
              return ($){.i = n.i == 0 ? 1 : n.i * fact.f(($){.i = n.i - 1}).i};
            }
            lambda;
          })};

  $ make_adder = ($){.f = ({
                       $ lambda($ n) {
                         return ($){.f = ({
                                      $ lambda($ x) {
                                        return ($){.i = n.i + x.i};
                                      }
                                      lambda;
                                    })};
                       }
                       lambda;
                     })};

  $ add1 = make_adder.f(($){.i = 1});

  $ mul3 = ($){.f = ({
                 $ lambda($ n) { return ($){.i = n.i * 3}; }
                 lambda;
               })};

  $ compose = ($){
      .f = ({
        $ lambda($ f, $ g) {
          return ($){.f = ({
                       $ lambda($ n) {
                         return ($){.i = f.f(($){.i = g.f(($){.i = n.i}).i}).i};
                       }
                       lambda;
                     })};
        }
        lambda;
      })};

  $ mul3add1 = compose.f(mul3, add1);

  printf("%d\n", fact.f(($){.i = 5}).i);
  printf("%d\n", mul3.f(($){.i = add1.f(($){.i = 10}).i}).i);
  printf("%d\n", mul3add1.f(($){.i = 10}).i);
  return 0;
}

最佳答案

这个例子主要建立在两个 GCC 扩展上:nested functions , 和 statement expressions .

嵌套函数 扩展允许您在另一个函数体内定义一个函数。常规 block 作用域规则适用,因此嵌套函数在调用时可以访问外部函数的局部变量:

void outer(int x) {
    int inner(int y) {
        return x + y;
    }
    return inner(6);
}

...
int z = outer(4)' // z == 10

statement expression 扩展允许您包装 C block 语句(您通常可以放在大括号中的任何代码:变量声明、for 循环等)以用于创造值(value)的环境。它看起来像括号中的 block 语句:

int foo(x) {
    return 5 + ({
        int y = 0;
        while (y < 10) ++y;
        x + y;
    });
}

...
int z = foo(6); // z == 20

包装 block 中的最后一条语句提供了值。所以它的工作方式与您想象的内联函数体非常相似。

结合使用这两个扩展,您可以定义一个可以访问周围作用域变量的函数体,并立即在表达式中使用它,从而创建一种基本的 lambda 表达式。由于语句表达式可以包含任何语句,嵌套函数定义是语句,函数名是值,语句表达式可以定义函数并立即返回指向该函数的指针周边表达:

int foo(int x) {
    int (*f)(int) = ({      // statement expression
        int nested(int y) { // statement 1: function definition
            return x + y;
        }
        nested;             // statement 2 (value-producing): function name
    });                     // f == nested

    return f(6); // return nested(6) == return x + 6
}

示例中的代码通过使用美元符号作为返回类型的缩短标识符( another GCC extension ,对示例的功能而言重要性要低得多)来进一步修饰它。 lambda在示例中不是关键字或宏(但美元应该使它看起来像一个),它只是在语句表达式的范围内定义的函数的名称(重复使用多次)。 C 的范围嵌套规则意味着在更深的范围内重用相同的名称(嵌套的“lambdas”)是完全可以的,特别是当没有期望主体代码将名称用于任何其他目的时(lambdas 通常是匿名的,所以函数不需要“知道”他们实际上是 调用 lambda )。

如果您阅读有关嵌套函数的 GCC 文档,您会发现这种技术非常有限。嵌套函数在其包含框架的生命周期结束时过期。这意味着它们无法归还,也无法真正有效地存储。它们可以通过指针向上传递到从包含框架调用的其他函数中,这些函数需要一个普通的函数指针,所以它们仍然非常有用。但是它们没有任何接近于真正 lambda 的灵 active ,lambda 拥有它们关闭的变量的所有权(共享或全部取决于语言),并且可以作为真值在所有方向上传递或存储以供以后使用程序完全不相关的部分。语法也相当笨拙,即使您将其包装在大量辅助宏中也是如此。

C 很可能会在该语言的下一版本(目前称为 C2x)中获得真正的 lambda。您可以阅读有关拟议表格的更多信息 here - 它看起来并不像这样(它复制了 Objective-C 中的匿名函数语法和语义)。以这种方式创建的函数的生命周期可以超出其创建范围;函数体是真表达式,不需要包含语句的 hack;并且函数本身是真正匿名的,没有像 lambda 这样的中间名称需要。


上述示例的 C2x 版本很可能看起来像这样:

#include <stdio.h>

int main(void) {
  typedef int (^ F)(int);

  __block F fact;  // needs to be mutable - block can't copy-capture
                   // its own variable before initializing it
  fact = ^(int n) {
    return n == 0 ? 1 : n * fact(n - 1);
  };

  F (^ make_adder)(int) = ^(int n) {
    return _Closure_copy(^(int x) { return n + x; });
  };

  F add1 = make_adder(1);

  F mul3 = ^(int n) { return n * 3; };

  F (^ compose)(F, F) = ^(F f, F g) {
    return _Closure_copy(^(int n) { return f(g(n)); });
  };

  F mul3add1 = compose(mul3, add1);

  printf("%d\n", fact(5));
  printf("%d\n", mul3(add1(10)));
  printf("%d\n", mul3add1(10));

  _Closure_free(add1);
  _Closure_free(mul3add1);

  return 0;
}

没有所有 union 的东西要简单得多。

(您现在可以在 Clang 中编译并运行这个修改后的示例 - 使用 -fblocks 标志启用 lambda 扩展,将 #include <Block.h> 添加到文件顶部,并将 _Closure_copy_Closure_free 替换为Block_copyBlock_release。)

关于c - 这是什么疯狂?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39733325/

相关文章:

c 二进制文件大于源文件

c - 编译 C 程序时出错

c - 与gcc中的共享库链接时如何为可执行文件生成位置相关代码?

c - 响应 isatty(3) 的文件

c - 在这种情况下,从用户在 C 中输入的字符串中提取子字符串的最佳方法是什么

python - 错误 : command 'x86_64-linux-gnu-gcc' failed with exit status 1 while installing pygsr,

c - 忽略包含文件时,拆分源没有输出,但也没有警告

c++ - Beaglebone 上的 Gtk+ 绑定(bind)(arm linux)

存储在字符串中的字符 - 如何在随机化时将它们存储在另一个字符串变量中

c - 结合使用 Arduino Motor Shield 和 Bluetooth Shield