编译原理之美
宫文学
北京物演科技CEO
立即订阅
8171 人已学习
课程目录
已完结 43 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么你要学习编译原理?
免费
实现一门脚本语言 · 原理篇 (13讲)
01 | 理解代码:编译器的前端技术
02 | 正则文法和有限自动机:纯手工打造词法分析器
03 | 语法分析(一):纯手工打造公式计算器
04 | 语法分析(二):解决二元表达式中的难点
05 | 语法分析(三):实现一门简单的脚本语言
06 | 编译器前端工具(一):用Antlr生成词法、语法分析器
07 | 编译器前端工具(二):用Antlr重构脚本语言
08 | 作用域和生存期:实现块作用域和函数
09 | 面向对象:实现数据和方法的封装
10 | 闭包: 理解了原理,它就不反直觉了
11 | 语义分析(上):如何建立一个完善的类型系统?
12 | 语义分析(下):如何做上下文相关情况的处理?
13 | 继承和多态:面向对象运行期的动态特性
实现一门脚本语言 · 应用篇 (2讲)
14 | 前端技术应用(一):如何透明地支持数据库分库分表?
15 | 前端技术应用(二):如何设计一个报表工具?
实现一门脚本语言 · 算法篇 (3讲)
16 | NFA和DFA:如何自己实现一个正则表达式工具?
17 | First和Follow集合:用LL算法推演一个实例
18 | 移进和规约:用LR算法推演一个实例
实现一门脚本语言 · 热点答疑与用户故事 (2讲)
19 | 案例总结与热点问题答疑:对于左递归的语法,为什么我的推导不是左递归的?
用户故事 | 因为热爱,所以坚持
编译原理 · 期中考试周 (1讲)
期中考试 | 来赴一场100分的约定吧!
免费
实现一门编译型语言 · 原理篇 (12讲)
20 | 高效运行:编译器的后端技术
21 | 运行时机制:突破现象看本质,透过语法看运行时
22 | 生成汇编代码(一):汇编语言其实不难学
加餐 | 汇编代码编程与栈帧管理
23 | 生成汇编代码(二):把脚本编译成可执行文件
24 | 中间代码:兼容不同的语言和硬件
25 | 后端技术的重用:LLVM不仅仅让你高效
26 | 生成IR:实现静态编译的语言
27 | 代码优化:为什么你的代码比他的更高效?
28 | 数据流分析:你写的程序,它更懂
29 | 目标代码的生成和优化(一):如何适应各种硬件架构?
30 | 目标代码的生成和优化(二):如何适应各种硬件架构?
实现一门编译型语言 · 应用篇 (2讲)
31 | 内存计算:对海量数据做计算,到底可以有多快?
32 | 字节码生成:为什么Spring技术很强大?
实现一门编译型语言 · 扩展篇 (3讲)
33 | 垃圾收集:能否不停下整个世界?
34 | 运行时优化:即时编译的原理和作用
35 | 案例总结与热点问题答疑:后端部分真的比前端部分难吗?
面向未来的编程语言 (3讲)
36 | 当前技术的发展趋势以及其对编译技术的影响
37 | 云编程:云计算会如何改变编程模式?
38 | 元编程:一边写程序,一边写语言
结束语 (1讲)
结束语 | 用程序语言,推动这个世界的演化
编译原理之美
登录|注册

08 | 作用域和生存期:实现块作用域和函数

宫文学 2019-08-30
目前,我们已经用 Antlr 重构了脚本解释器,有了工具的帮助,我们可以实现更高级的功能,比如函数功能、面向对象功能。当然了,在这个过程中,我们还要克服一些挑战,比如:
如果要实现函数功能,要升级变量管理机制;
引入作用域机制,来保证变量的引用指向正确的变量定义;
提升变量存储机制,不能只把变量和它的值简单地扔到一个 HashMap 里,要管理它的生存期,减少对内存的占用。
本节课,我将借实现块作用域和函数功能,带你探讨作用域和生存期及其实现机制,并升级变量管理机制。那么什么是作用域和生存期,它们的重要性又体现在哪儿呢?
“作用域”和“生存期”是计算机语言中更加基础的概念,它们可以帮你深入地理解函数、块、闭包、面向对象、静态成员、本地变量和全局变量等概念。
而且一旦你深入理解,了解作用域与生存期在编译期和运行期的机制之后,就能解决在学习过程中可能遇到的一些问题,比如:
闭包的机理到底是什么?
为什么需要栈和堆两种机制来管理内存?它们的区别又是什么?
一个静态的内部类和普通的内部类有什么区别?
了解上面这些内容之后,接下来,我们来具体看看什么是作用域。

作用域(Scope)

作用域是指计算机语言中变量、函数、类等起作用的范围,我们来看一个具体的例子。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《编译原理之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(15)

  • Johnson
    现在课程的做法相当于AST之后直接解析执行了,所有的逻辑都堆在AST和紧接着的语义分析,没有把AST转化成IR,然后在这个IR上做各种事情,最后再到interpreter执行。是因为前期为了简单起见,所以先这么直观的来么?

    作者回复: 因为目前是在讲前端,所以就先不引入IR。
    同时也是在告诉同学们,哪怕我们只拿到了AST,也已经能做很多事情了。

    IR在后端部分会讲。我会给出一个自己设计的IR的例子,用IR重新实现部分功能。然后再去采用LLVM的IR。

    2019-08-30
    6
  • 许童童
    变量的使用范围由作用域决定,作用域由词法规则决定,词法分析生成作用域链,之后查找变量就沿着这条作用域链查找,与函数调用栈就没有关系了。一般函数的生存期就是出栈后就结束了,如果是引用对象会在本次GC中回收,如果产生了闭包,那就要等到引用闭包的变量销毁,生存期才结束。

    作者回复: 很准确,很清晰!

    2019-08-30
    4
  • Geek_f9ea2d
    functionDeclaration
        : typeTypeOrVoid? IDENTIFIER formalParameters ('[' ']')*
          (THROWS qualifiedNameList)?
          functionBody
    中的('[' ']')* 这个没明白什么意思,函数的声明,我觉得这样就够了:typeTypeOrVoid? IDENTIFIER formalParameters

    作者回复: 这是直接照搬的Java的语法。这是对数组的支持。目前playscript并没有支持数组,但语法也就先这么放着了。

    2019-09-11
    1
    3
  • mcuking
    其实 js 的 es6 版本已经支持块级作用域,可以用 let const 声明

    作者回复: 是的,我注意到了。
    严格的表述这样写可能比较好:如果只是像java和c那样声明变量,就没有块作用域:-)

    2019-09-07
    1
    2
  • ZYS
    宫老师,可否兼顾一下用c++的学员,介绍一下cpp版本playscript如何在visual studio2010或更高的版本运行?

    作者回复: 讲后端部分的时候,主要是用cpp版本实现的。那部分的指导资料我整理一下,写一个README.md,尽快更新到Github和码云上。

    先简单说一下:
    1.如果仅仅用cpp版本的Antlr,这个比较简单,你做练习的时候可以试用一下。
    2.把Antlr和LLVM一起用的时候,要配置的东西更多一些,好在有cmake。

    2019-08-31
    2
  • 北冥Master
    牛逼,越来越深入了,看的有点吃力了

    作者回复: 后几讲涉及的都是语义功能,并涉及了一部分运行期技术(给后端技术部分提前做铺垫)。
    语义上的差别是每种语言真正的差别,但底层有一些共通的机制。搞搞明白对我们学各种语言都有好处。

    2019-08-30
    2
  • 吃瓜群众路人丙
    也就是说,栈里的上一级栈桢,不一定是 Scope 的父节点。
    老师能举个反例吗

    作者回复: 递归函数的调用。
    int a;
    int foo(){
      a = a+1;
      if (a<10){
         return foo();
      }
      else{
        return a;
      }
    }

    在递归调用的时候,你在函数里仍然可以访问全局变量。这个全局变量不在上一级的函数栈桢里。而是在最底下那个全局变量的栈桢。

    2019-09-22
    1
  • Geek_89bbab
    private 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的作用域来判断。
    */

    2019-09-01
    1
  • E
    有可以生成语义分析器的工具吗
    2019-12-04
  • 宫老师,请教两个问题,C++部分,playscript-cpp这个目录下,
    (1)antlr4.7.2-runtime 这个目录包含antlr4.7.2的源码吗,还是只是包含由antlr生成的parser等程序编译所需要的文件?antlr4.7.2这个工具有没有C++的实现源码?
    (2)lib目录中放的a文件和dylib文件是干嘛的呢?

    作者回复: 这里的代码我还没整理整齐。在后端部分的课程放出时,我会整理好。先简单的说一下:
    Antlr本身是Java写的,但它可以生成其他语言的编译工具。这些编译工具呢,要调用一些基础的功能,这些功能就是针对不同语言的运行时(runtime)。
    用C++编程时,需要用到头文件,然后用到它的库。a文件和dylib就是库。

    2019-10-01
  • David
    老师你好,javascript文件中定义一个function或者一个class,这个function也是放入全局作用域的栈帧中的吗?这些class在运行器中是放在栈帧中的吗?

    作者回复: 在第21讲,里面提了内存布局的设计问题,一般会分为代码区、静态数据区、栈区、堆区。
    如果只是function和class的定义,应该放在代码区才对。
    在运行某个函数的时候,运行期动态生成的临时数据,才会放入栈里。
    但每种语言在这方面的设计有它自己很大的灵活性。比如静态编译的语言和解释型语言的机制就很不一致。
    08讲的栈,还是比较概念化的,简单的模拟了一个栈。21、22、23三讲,有一个符合标准的调用约定的栈的实现,也就是说跟C语言的实现是一致的,甚至可以跟C语言生成的二进制目标文件链接到一起。

    2019-09-28
  • 赖阿甘
    // 函数声明
    functionDeclaration
        : typeTypeOrVoid? IDENTIFIER formalParameters ('[' ']')* //函数参数后面的方括号是做什么用的
          functionBody
        ;
    老师请问函数参数后面的方括号是做什么用的

    作者回复: 是用来支持数组的。我们并没有实现数组特性,但语法上留在那了。

    2019-09-23
  • 曾经瘦过
    基本看懂了 在巩固一下 跑跑代码 感觉越来越深入了 大概能明白为啥学好编译原理可以更好更快的学其他语言
    2019-09-19
  • 沉淀的梦想
    用PlayScript的代码运行课程中的示例时会报一个空指针异常
    String script = "int age = 44; for(int i = 0;i<10;i++) { age = age + 2;} int i = 8;";

    Exception in thread "main" java.lang.NullPointerException
    at play.ASTEvaluator.visitStatement(ASTEvaluator.java:617)
    at play.ASTEvaluator.visitBlockStatement(ASTEvaluator.java:363)
    at play.ASTEvaluator.visitBlockStatements(ASTEvaluator.java:723)
    at play.ASTEvaluator.visitProg(ASTEvaluator.java:733)
    at play.PlayScriptParser$ProgContext.accept(PlayScriptParser.java:2031)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at play.PlayScriptCompiler.Execute(PlayScriptCompiler.java:28)
    at play.PlayScript.main(PlayScript.java:98)

    作者回复: 1.是运行双引号内部的部分,不是连String script= 也带上。
    2.是使用playscript-java这个工程吗?别用错了工程。
    如果还有问题,继续给我提问!
    希望不影响你继续动手实践的热情!

    2019-09-02
    1
  • 李懂
    原来栈里放的栈贞,栈贞是Scope,类似执行上下文,里面保存了变量!以前一直以为进栈,是放的执行函数体,跟上脚步!

    作者回复: 这个栈桢还是拿java模拟的,让大家有个概念。到学后端的时候,那里有更物理的栈桢实现。到时候你可以进一步加深一下认识:-D

    2019-08-30
收起评论
15
返回
顶部