08 | 作用域和生存期:实现块作用域和函数
08 | 作用域和生存期:实现块作用域和函数
讲述:宫文学
时长13:37大小12.46M
作用域(Scope)
生存期(Extent)
实现作用域和栈
实现块作用域
实现函数功能
课程小结
一课一思
赞 9
提建议
精选留言(26)
- 聆听v风声2020-06-07示例代码建议每节课加个tag或分支之类的,现在后面的人来看,都是最终完全版的代码..共 1 条评论27
- 许童童2019-08-30变量的使用范围由作用域决定,作用域由词法规则决定,词法分析生成作用域链,之后查找变量就沿着这条作用域链查找,与函数调用栈就没有关系了。一般函数的生存期就是出栈后就结束了,如果是引用对象会在本次GC中回收,如果产生了闭包,那就要等到引用闭包的变量销毁,生存期才结束。
作者回复: 很准确,很清晰!
26 - 幻境之桥2020-04-01我在上图中展现了这种情况,在调用 fun 函数的时候,栈里一共有三个栈桢:全局栈桢、main() 函数栈桢和 fun() 函数栈桢,其中 main() 函数栈桢的 parentFrame 和 fun() 函数栈桢的 parentFrame 都是全局栈桢。 老师这里没有明白为什么 fun()函数栈桢的 parentFrame 是全局栈桢而不是 main()的函数栈桢 这个 parentFrame 怎么定义呢?展开
作者回复: 因为fun函数和main函数都是在全局作用域中定义的。fun中如果有一个变量,不是在本地声明的,那到哪里去找呢?要到全局作用域中去找,而不是到main函数中找。 我在课程里没有介绍一个概念:词法作用域(Lexical Scope),等以后找机会加上。 词法作用域又叫做静态作用域,也就是说,程序里用到的变量,在编译时,就知道是哪个变量定义。因为完全是根据声明时的位置关系来做变量引用解析的。
共 5 条评论14 - Johnson2019-08-30现在课程的做法相当于AST之后直接解析执行了,所有的逻辑都堆在AST和紧接着的语义分析,没有把AST转化成IR,然后在这个IR上做各种事情,最后再到interpreter执行。是因为前期为了简单起见,所以先这么直观的来么?
作者回复: 因为目前是在讲前端,所以就先不引入IR。 同时也是在告诉同学们,哪怕我们只拿到了AST,也已经能做很多事情了。 IR在后端部分会讲。我会给出一个自己设计的IR的例子,用IR重新实现部分功能。然后再去采用LLVM的IR。
10 - 吃瓜群众路人丙2019-09-22也就是说,栈里的上一级栈桢,不一定是 Scope 的父节点。 老师能举个反例吗
作者回复: 递归函数的调用。 int a; int foo(){ a = a+1; if (a<10){ return foo(); } else{ return a; } } 在递归调用的时候,你在函数里仍然可以访问全局变量。这个全局变量不在上一级的函数栈桢里。而是在最底下那个全局变量的栈桢。
7 - mcuking2019-09-07其实 js 的 es6 版本已经支持块级作用域,可以用 let const 声明
作者回复: 是的,我注意到了。 严格的表述这样写可能比较好:如果只是像java和c那样声明变量,就没有块作用域:-)
共 2 条评论6 - ZYS2019-08-31宫老师,可否兼顾一下用c++的学员,介绍一下cpp版本playscript如何在visual studio2010或更高的版本运行?
作者回复: 讲后端部分的时候,主要是用cpp版本实现的。那部分的指导资料我整理一下,写一个README.md,尽快更新到Github和码云上。 先简单说一下: 1.如果仅仅用cpp版本的Antlr,这个比较简单,你做练习的时候可以试用一下。 2.把Antlr和LLVM一起用的时候,要配置的东西更多一些,好在有cmake。
3 - Geek_f9ea2d2019-09-11functionDeclaration : typeTypeOrVoid? IDENTIFIER formalParameters ('[' ']')* (THROWS qualifiedNameList)? functionBody 中的('[' ']')* 这个没明白什么意思,函数的声明,我觉得这样就够了:typeTypeOrVoid? IDENTIFIER formalParameters展开
作者回复: 这是直接照搬的Java的语法。这是对数组的支持。目前playscript并没有支持数组,但语法也就先这么放着了。
共 2 条评论3 - Aaaaaaaaaaayou2020-02-05老师,playscript-java/src/main/play/DefaultFunctionType.java 中 public static boolean isType(FunctionType type1, FunctionType type2) 函数中 List<Type> paramTypes2 = type1.getParamTypes(); 是不是写错了? type1 应该改为 type2
作者回复: 是的。谢谢你细心的阅读代码。 我已经在github里更新了,并且在commit comment中专门感谢了你:)
2 - 北冥Master2019-08-30牛逼,越来越深入了,看的有点吃力了
作者回复: 后几讲涉及的都是语义功能,并涉及了一部分运行期技术(给后端技术部分提前做铺垫)。 语义上的差别是每种语言真正的差别,但底层有一些共通的机制。搞搞明白对我们学各种语言都有好处。
2 - Geek_6304e32022-02-11老师,这个java项目的怎么跑起来呢,不懂java。2
- Geek_6304e32022-02-11老师可以照顾下前端的同学吗?有没有JavaScript版的呢2
- 草戊2019-12-22//全局变量 int i = 0; { //这里引用的是全局变量 i = 2; println(i); //输出:2 //允许在块里新创建一个同名的变量 int i = 3; println(i); //输出:3 } 您好,上面例子中 【i = 2;】这句话在块中使用全局变量,但是实际上此块中也定义了i变量,只不过位置比较靠后,这种情况,本文中的作用域有办法解决吗?如果优先在本块中查找,那么会不会找到自己块中的i,而不是全局的i呢?展开
作者回复: 实际上,在每一行代码,可以见到的“可用表达式”的集合都是不一样的。也就是说,在每一行,你可以反问的变量都是不同的。 比如,在int i = 3之后,你访问的i,和在它前面访问的i,是不一样的。 也就是说,块作用域,并不是在整个块里可见,而是在这个块里变量声明之后才可见。 在28讲,数据流分析中,专门有活跃性分析的一个算法,会教会你确定在每个位置的可用变量。
1 - Geek_89bbab2019-09-01private void pushStack(StackFrame frame) { // 如果新加入的frame是当前frame的下一级,则入栈 if (stack.size() > 0) { for (int i = stack.size()-1; i>0; i--){ StackFrame f = stack.get(i); if (f.scope.enclosingScope == frame.scope.enclosingScope){ frame.parentFrame = f.parentFrame; break; } else if (f.scope == frame.scope.enclosingScope){ frame.parentFrame = f; break; } else if (frame.object instanceof FunctionObject){ FunctionObject functionObject = (FunctionObject)frame.object; if (functionObject.receiver != null && functionObject.receiver.enclosingScope == f.scope) { frame.parentFrame = f; break; } } } if (frame.parentFrame == null){ frame.parentFrame = stack.peek(); } } stack.push(frame); if (traceStackFrame){ dumpStackFrame(); } } 老师可以解释一下这个函数吗?展开
作者回复: 我往代码里加了注释,你可以更新一下看看! 我也把注释拷贝到这里。 里面有些特性,比如一等公民函数,是还没讲到的,10讲就会讲。 第一个if: /* 如果新加入的栈桢,跟某个已有的栈桢的enclosingScope是一样的,那么这俩的parentFrame也一样。 因为它们原本就是同一级的嘛。 比如: void foo(){}; void bar(foo()); 或者: void foo(); if (...){ foo(); } */ 第二个if: /* 如果新加入的栈桢,是某个已有的栈桢的下一级,那么就把把这个父子关系建立起来。比如: void foo(){ if (...){ //把这个块往栈桢里加的时候,就符合这个条件。 } } 再比如,下面的例子: class MyClass{ void foo(); } MyClass c = MyClass(); //先加Class的栈桢,里面有类的属性,包括父类的 c.foo(); //再加foo()的栈桢 */ 第3个if: /* 这是针对函数可能是一等公民的情况。这个时候,函数运行时的作用域,与声明时的作用域会不一致。 我在这里设计了一个“receiver”的机制,意思是这个函数是被哪个变量接收了。要按照这个receiver的作用域来判断。 */
共 2 条评论1 - hYector2021-10-09请教老师,既然说每个变量都有自己的作用域,但是在作用域实现部分,突然变成全局,main(), if块等作用域,这是观念是怎么切换过来的?
- pencilCool2021-07-09嵌套的两个大括号,内外层出现同名变量:C 语言允许 覆盖,java 不允许,javascript 指向同一个。
- Geek_satsuki2020-08-07String script = "int b= 10; int myfunc(int a) {return a+b+3;} myfunc(2);"; 测试函数出现NULLException script = int b= 10; int myfunc(int a) {return a+b+3;} myfunc(2); Exception in thread "main" java.lang.NullPointerException at play.TypeResolver.exitFormalParameter(TypeResolver.java:105) at play.PlayScriptParser$FormalParameterContext.exitRule(PlayScriptParser.java:940)展开
- 漏网之渔2020-03-28老师,lValue.setValue(paramValues.get(i)); 为什么执行完这一行,实参的值就能进入functionFrame了?调试了一下代码,发现进入了MyLVaue类的setValue方法,这个MyLvalue类有一个private PlayObject valueContainer属性,改变了这个属性中的Map<Variable, Object> fields值,但是它是怎么改变frame栈帧里的值的呢?这一点我调试了之后不太明白;对MyLvalue和frame的关系不太理解,请老师指点。
作者回复: 在示例程序中,我是用一个PlayObject来模拟栈帧的,它里面用了一个HashMap来存放变量值。这些变量可能是一个函数的本地变量,也可能是对象的成员变量。 而MyLValue,实际上就是对一个栈帧中的变量的引用,相当于C语言中的一个变量地址。基于这个引用,就可以找到准确的变量存储位置,并做修改。
共 2 条评论 - 漏网之渔2020-03-27老师,这里的LValue和PlayObject分别扮演的角色功能是什么样的,什么时候会用到他们;看了源代码,自己有点理不清楚。
作者回复: 在示例代码里,PlayObject我就是拿它模拟一个栈帧的,里面放着各个变量的值。 LValue是左值的意思,也就是可以出现在赋值符号的左边。它得是一个对象引用(或C语言中的地址),以便往里面写新的值。而赋值符号右边的,叫做右值,它可以是一个具体的数值。 左值可以当做右值用,但右值不可以当成左值用。 在给AST做语义分析的时候,可以分析出哪个标识符实际是个左值,这样的话,遍历AST时,要获得变量引用,而不是获得变量值。
- E2019-12-04有可以生成语义分析器的工具吗
作者回复: 没有。 但是antlr支持在语法规则里嵌入一些代码,在语法分析的过程中完成一些语义分析工作。