嵌套函数的实现 [英] Implementation of nested functions

查看:143
本文介绍了嵌套函数的实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I recently found out that gcc allows the definition of nested function. In my opinion, this is a cool feature, but I wonder how to implement it.

虽然它肯定是不难通过传递上下文指针作为一个隐藏的参数来实现的嵌套函数直接调用,GCC还允许一个指针指向一个嵌套函数,这指针传递到任意其他的功能,这反过来能调用上下文的嵌套函数。因为调用嵌套函数的函数只有嵌套函数调用的类型,它显然无法通过上下文指针。

While it is certainly not difficult to implement direct calls of nested functions by passing a context pointer as a hidden argument, gcc also allows to take a pointer to a nested function and pass this pointer to an arbitrary other function that in turn can call the nested function of the context. Because the function that calls the nested function has only the type of the nested function to call, it obviously can't pass a context pointer.

我知道,像哈斯克尔说其他语言有更令人费解的调用约定允许部分应用程序来支持这样的东西,但我看不出有什么办法做到这一点在C怎么可能实现呢?

I know, that other languages like Haskell that have a more convoluted calling convention allow partial application to support such stuff, but I see no way to do that in C. How is it possible to implement this?

下面是说明问题的情况下的一个小例子:

Here is a small example of a case that illustrates the problem:

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

  return f(x,g);
}

此函数调用调用从上下文返回一个计数器,并在同一时间递增它的功能的功能

This function calls a function that calls a function that returns a counter from the context and increments it at the same time.

推荐答案

GCC使用一种叫做蹦床。

GCC uses something called a trampoline.

信息:<一href=\"http://gcc.gnu.org/onlinedocs/gccint/Trampolines.html\">http://gcc.gnu.org/onlinedocs/gccint/Trampolines.html

一个蹦床是一块code那GCC会在堆栈中,当你需要一个指向一个嵌套函数使用。在您的code,因为你通过先按g 作为参数传递给函数调用蹦床是必要的。蹦床初始化一些寄存器,这样的嵌套函数可以参考变量外部函数,那么它跳转到嵌套函数本身。蹦床是非常小的 - 你反弹掀起了蹦床,进入嵌套函数体

A trampoline is a piece of code that GCC creates in the stack to use when you need a pointer to a nested function. In your code, the trampoline is necessary because you pass g as a parameter to a function call. A trampoline initializes some registers so that the nested function can refer to variables in the outer function, then it jumps to the nested function itself. Trampolines are very small -- you "bounce" off a trampoline and into the body of the nested function.

使用嵌套函数这种方式需要一个可执行堆栈,它不鼓励这些天。是不是真的有周围没有任何办法。

Using nested functions this way requires an executable stack, which is discouraged these days. There is not really any way around it.

蹦床解剖:

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

Here is an example of a nested function in GCC's extended 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);
}

这很简单,我们可以看到它是如何工作。下面是导致组装,减去一些东西:

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语法):

You'll notice that most of what it does is write registers and constants to the stack. We can follow along, and find that at SP+4 it places a 19 byte object with the following data (in GAS syntax):


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

这是很容易的通过反汇编器来运行。假设 $ nested.1594 0x01234567 %RSP 0x0123456789abcdef 。由此产生的拆卸,由 objdump的提供的是:

This is easy enough to run through a disassembler. Suppose that $nested.1594 is 0x01234567 and %rsp is 0x0123456789abcdef. The resulting disassembly, provided by 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 并跳转到嵌套函数的身体。嵌套函数体看起来是这样的:

So, the trampoline loads the outer function's stack pointer into %r10 and jumps to the nested function's body. The nested function body looks like this:

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

正如你所看到的,嵌套函数使用%R10 来访问外部函数的变量。

As you can see, the nested function uses %r10 to access the outer function's variables.

当然,这是相当愚蠢的蹦床是的放大的比嵌套函数本身。你可以很容易做的更好。但不是很多人使用此功能,而这样一来,蹦床可以保持相同的大小(19字节)无论嵌套函数有多大。

Of course, it's fairly silly that the trampoline is larger than the nested function itself. You could easily do better. But not very many people use this feature, and this way, the trampoline can stay the same size (19 bytes) no matter how large the nested function is.

最后说明:在组件的底部,有一个最后的指令:

Final note: At the bottom of the assembly, there is a final directive:


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

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

This instructs the linker to mark the stack as executable.

这篇关于嵌套函数的实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆