它们被称为“蹦床”。这是我对它们用途的理解,虽然我不是 GNAT 专家,所以我的一些理解可能是错误的。
背景:Ada(与 C 不同)支持嵌套子程序。嵌套子程序能够访问封闭子程序的局部变量。例如:
procedure Outer is
Some_Variable : Integer;
procedure Inner is
begin
...
Some_Variable := Some_Variable + 1;
...
由于每个过程都有自己的堆栈帧,其中包含自己的局部变量,因此必须有一种方法Inner
可以获取Outer
的堆栈帧,以便它可以Some_Variable
在调用时访问 ,Outer
或者Inner
调用Outer
其他一些嵌套的子程序调用Inner
。一个典型的实现是将隐藏参数传递给Inner
,通常称为“静态链接”,它指向Outer
的堆栈帧。现在Inner
可以使用它来访问Some_Variable
.
当您使用 时,乐趣就开始Inner'Access
了,这是一种access procedure
类型。这可用于将地址存储在类型Inner
的变量中access procedure
。其他子程序稍后可以使用该变量间接调用程序。如果使用'Access
,则必须在内部声明该变量Outer
——您不能将过程访问存储在外部的变量中Outer
,因为之后有人可以在Outer
退出之后调用它,并且其局部变量不再存在。GNAT 和其他 Ada 编译器具有'Unrestricted_Access
绕过此限制的属性,因此Outer
可以调用一些间接调用的外部子程序Inner
. 但是你在使用它的时候必须非常小心,因为如果你在错误的时间调用它,结果会很严重。
无论如何,问题出现是因为 whenInner'Access
存储在变量中,后来用于Inner
间接调用,调用时必须使用带有静态链接的隐藏参数Inner
。那么间接调用者如何知道要传递什么静态链接呢?
一种解决方案(Irvine Compiler 的,可能还有其他的)是让这种访问类型的变量有两个值——过程地址和静态链接(所以 anaccess procedure
是一个“胖指针”,而不是一个简单的指针)。然后对该过程的调用将始终传递静态链接,以及其他参数(如果有)。[在 Irvine Compiler 的实现中,如果指针内部的静态链接实际上指向一个全局过程,则该指针内部的静态链接将为空,因此代码知道在这种情况下不传递隐藏参数。] 缺点是这在以下情况下不起作用将过程地址作为回调参数传递给 C 例程(这在位于 C 图形库(如 gtk)之上的 Ada 库中非常常见)。C 库例程不知道如何处理这样的胖指针。
GNAT 使用或曾经使用过蹦床来解决这个问题。基本上,当它看到 时Inner'Unrestricted_Access'
,它会即时生成新代码(“蹦床”)。此蹦床Inner
使用正确的静态链接调用(链接的值将嵌入代码中)。然后访问值将是一个细指针,只有一个地址,即蹦床的地址。因此,当 C 代码调用回调时,它会调用蹦床,然后蹦床将隐藏参数添加到参数列表并调用Inner
.
这解决了问题,但在堆栈上生成蹦床时会产生安全问题。
编辑:当我用现在时提到 GNAT 的实现时我犯了错误。几年前我最后一次看到这个,我真的不知道 GNAT 是否仍然这样做。[Simon 对此有更好的信息。] 顺便说一下,我确实认为可以使用蹦床但不将它们放在堆栈上,我认为这会减少安全问题。当我上次对此进行调查时,如果我没记错的话,Windows 已经开始阻止堆栈上的代码被执行,但它也允许程序请求可用于动态生成可执行代码的内存。