嵌套函数的实现

2023-12-12

我最近发现gcc允许定义嵌套函数。在我看来,这是一个很酷的功能,但我不知道如何实现它。

虽然通过传递上下文指针作为隐藏参数来实现对嵌套函数的直接调用当然并不困难,但 gcc 还允许获取指向嵌套函数的指针并将该指针传递给任意其他函数,该函数又可以调用嵌套函数上下文的函数。因为调用嵌套函数的函数只有要调用的嵌套函数的类型,所以显然不能传递上下文指针。

我知道,其他语言(例如 Haskell)具有更复杂的调用约定,允许部分应用程序支持此类内容,但我认为在 C 中没有办法做到这一点。如何实现这一点?

这是一个说明问题的小例子:

int foo(int x,int(*f)(int,int(*)(void))) {
  int counter = 0;
  int g(void) { return counter++; }

  return f(x,g);
}

该函数调用一个函数,该函数调用一个从上下文返回计数器并同时递增该计数器的函数。


GCC 使用一种称为蹦床的东西。

信息:http://gcc.gnu.org/onlinedocs/gccint/Trampolines.html

蹦床是 GCC 在堆栈中创建的一段代码,当您需要指向嵌套函数的指针时可以使用它。在您的代码中,蹦床是必要的,因为您通过了g作为函数调用的参数。蹦床初始化一些寄存器,以便嵌套函数可以引用外部函数中的变量,然后跳转到嵌套函数本身。蹦床非常小——您从蹦床“弹跳”到嵌套函数的主体中。

以这种方式使用嵌套函数需要一个可执行堆栈,但现在不鼓励这样做。实际上没有任何办法可以解决这个问题。

蹦床剖析:

下面是 GCC 扩展 C 中嵌套函数的示例:

void func(int (*param)(int));

void outer(int x)
{
    int nested(int y)
    {
        // If x is not used somewhere in here,
        // then the function will be "lifted" into
        // a normal, non-nested function.
        return x + y;
    }
    func(nested);
}

它非常简单,所以我们可以看看它是如何工作的。这是最终的组装结果outer,减去一些东西:

subq    $40, %rsp
movl    $nested.1594, %edx
movl    %edi, (%rsp)
leaq    4(%rsp), %rdi
movw    $-17599, 4(%rsp)
movq    %rsp, 8(%rdi)
movl    %edx, 2(%rdi)
movw    $-17847, 6(%rdi)
movw    $-183, 16(%rdi)
movb    $-29, 18(%rdi)
call    func
addq    $40, %rsp
ret

您会注意到它所做的大部分工作是将寄存器和常量写入堆栈。我们可以继续观察,发现在 SP+4 处它放置了一个 19 字节的对象,其中包含以下数据(采用 GAS 语法):



.word -17599
.int $nested.1594
.word -17847
.quad %rsp
.word -183
.byte -29
  

这很容易通过反汇编程序运行。假设$nested.1594 is 0x01234567 and %rsp is 0x0123456789abcdef。由此产生的反汇编结果由提供objdump, is:



   0:   41 bb 67 45 23 01       mov    $0x1234567,%r11d
   6:   49 ba ef cd ab 89 67    mov    $0x123456789abcdef,%r10
   d:   45 23 01 
  10:   49 ff e3                rex.WB jmpq   *%r11
  

因此,蹦床将外部函数的堆栈指针加载到%r10并跳转到嵌套函数的主体。嵌套函数体如下所示:

movl    (%r10), %eax
addl    %edi, %eax
ret

如您所见,嵌套函数使用%r10访问外部函数的变量。

当然,蹦床是相当愚蠢的larger比嵌套函数本身。你可以轻松地做得更好。但使用这个功能的人并不多,这样,无论嵌套函数有多大,trampoline 都可以保持相同的大小(19 字节)。

最后说明:在程序集的底部,有一个最终指令:



.section        .note.GNU-stack,"x",@progbits
  

这指示链接器将堆栈标记为可执行文件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌套函数的实现 的相关文章

随机推荐