Perl中闭包与local sub的局部变量可见性差异问题
问题原因
示例中不带任何作用域修饰符的嵌套sub inner,根本不是Perl 5.18新增的本地(词法)子程序,而是Perl从诞生之初就存在的全局命名子程序。
- Perl 5.18引入的词法本地子程序必须显式加
my/state/our修饰,写成my sub inner($) { ... }才是仅在outer作用域内可见的本地子程序;直接写sub inner不管嵌套在什么位置,都会被注册到当前包的全局符号表,整个包内外都能调用,多次调用outer时Perl还会弹出Subroutine inner redefined的警告,就是因为它是全局唯一的。 - 全局命名子程序在编译阶段就完成了定义和符号绑定,生命周期贯穿整个程序运行期,只会被编译一次。而
outer里的my $x是每次调用outer时才会在当前调用的词法暂存区创建的局部变量,全局的inner根本无法动态绑定每一次调用生成的$x,编译时也找不到对应可用的变量绑定,才会抛出Variable $x is not available错误。如果开启了警告,Perl会提前报Variable "$x" will not stay shared,说的就是这种全局嵌套sub无法捕获外层动态局部变量的问题。 - 闭包写法能正常运行,是因为匿名子程序
sub ($) { ... }是运行到对应代码行时才创建的,每次调用outer都会生成一个新的匿名sub实例,自动捕获当前作用域里的$x形成闭包,访问自然没有问题。
只要把示例里的sub inner改成my sub inner,就是正确使用Perl 5.18词法子程序的写法,可以正常捕获$x,行为和闭包一致。
和Pascal本地子程序的本质差异
Perl的词法本地子程序和Pascal的本地子程序核心差异在实现逻辑和子程序的能力边界上:
- Pascal的本地子程序基于调用栈的静态链实现:外层函数的局部变量存在调用栈上,外层函数返回后对应的栈帧直接销毁,本地子程序不能作为返回值传到外层作用域之外,否则会访问已经销毁的栈帧出现悬空指针,只能在当前外层函数的调用生命周期内使用,属于受限的向下闭包。
- Perl的词法变量是基于引用计数在堆上分配的,不管是匿名闭包还是
my修饰的命名本地子程序,只要捕获了外层词法变量,变量的生命周期就会自动延长,直到所有引用它的子程序被回收为止。因此Perl的本地子程序是完全的一等公民,可以作为参数、返回值任意传递,不存在悬空引用问题,这是两者最核心的设计差异。
内容的提问来源于stack exchange,提问作者U. Windl
相关产品推荐
相关产品推荐

