精华内容
下载资源
问答
  • 终结符和非终结符 Java finalize()方法允许对象执行特殊的清理操作,然后最终由垃圾回收器将其丢弃。 这是相当知名的,这种语言功能有问题,而且它是安全的使用被限制在一个很窄的一组用例,主要例子是“安全网”...

    终结符和非终结符

    Java finalize()方法允许对象执行特殊的清理操作,然后最终由垃圾回收器将其丢弃。 这是相当知名的,这种语言功能有问题,而且它是安全的使用被限制在一个很窄的一组用例,主要例子是“安全网”模式,其中一个终结的情况下使用对象的所有者忘记调用显式终止方法。 不幸的是,鲜为人知的是,即使这样的用例都很脆弱,而且如果没有特殊的预防措施,也会失败。 与人们普遍认为的PhantomReference相反,它通常被认为是终结器的一个很好的替代品,它也遇到了相同的基本问题。

    在深入探讨此问题之前,回顾一下Java终结器的一般否定点很有用。

    终结者的普遍否定

    1.执行被推迟,甚至可能永远不会发生。

    垃圾收集器可以选择推迟清理使用过的对象,直到容量变得更加有限或直到某些执行特征(例如可能减少的负载)表明吉祥的收集期已经实现为止。 此行为的缺点是,终结器要到其对象被收集后才能运行。 此外,终结器方法通常在较小的线程池中执行,从而导致额外的延迟。 当终结器的编写不当时,问题变得更加复杂,这可能会导致执行阻塞操作的可能性,这些阻塞操作可能会严重延迟其他终结器的执行,因为它们都倾向于共享同一池。 如果程序过早退出,或者垃圾回收器具有大量资源,则终结器可能永远不会执行。 因此,决不能以要求终结器采取措施的方式设计类。

    2.包含终结器的对象的垃圾收集比没有垃圾的收集要昂贵得多。

    具有finalize()方法的对象需要更多的工作来跟踪垃圾回收器,并且finalize方法的执行要求要求垃圾回收器保持与之关联的所有内存,直到执行成功完成为止。 这意味着通常需要收集者重新访问对象,可能需要整个单独的过程。 因此,具有大量实例计数和较短寿命的对象上的终结器可能会引入主要的性能问题。

    3.在同一对象图中的对象上同时执行终结器可能会产生不良结果。

    这可能导致数据结构中节点之间经常相互引用的不直观行为。 这些节点上的终结器可以同时以任意顺序调用,如果它们访问各自对等方的状态,则可能导致损坏状态。 必须注意确保特定的订单或应对由此产生的波动性。

    4.终结器中未捕获的异常将被忽略,并且从不报告

    终结器需要适当的异常处理并在发生故障时进行日志记录。 否则,关键的调试数据将丢失,并且对象可能会保持意外状态。

    5.终结器可以在损坏状态下无意中复活对象

    如果“ this”引用从finalize()方法中泄漏出来,则该对象仍然可见,但处于半清除状态,可能导致应用程序其他部分的错误。

    总而言之,这些因素中的一个或多个因素的组合排除了确定性语言中对象清理工具所共有的大多数用例,例如C ++中的析构函数,它们与定义明确的范围或显式的自由操作相关。 相反,Java是围绕面向调用者的清理而设计的。

    适当的资源清理

    Java中正确的资源清理应该面向调用者。 这种方法要求资源提供一种“关闭”方法,并且调用者必须使用Java 7的try-with-resources(或可选地try / finally)。 这样可以立即进行确定性清除。 即使资源提供了自己的finalize()方法,也应该使用此机制来开发曾经使用资源(带有close()或其他终止方法的代码)的所有代码。 这样做可以大大减少潜在的错误并提高性能。

    示例1:正确的尝试资源

    try (FileInputStream file = new FileInputStream( “file" )) {
       // Try with resources will call file.close when this block is done
    }
    

    但是,如果调用者忘记正确使用try-with-resources(或try / finally)机制,API设计人员通常希望或被合同要求为重资源添加附加的保护措施。 作为后者的一个示例,JNDI上下文通常与诸如网络连接之类的资源相关联,并且其Javadoc明确声明调用close()是可选的,如果未调用,清理仍将发生。

    瑕疵

    为了防止此类遗漏,唯一可用的选项是使用“安全网终结器”模式或使用PhantomReference。 不幸的是,如果不采取预防措施,这些选择将会并且确实会失败。

    示例2:安全网络终结器有缺陷

    public void work() throws Exception {
      FileOutputStream stream = this .stream;
      stream.write( PART1 );
      stream.write( PART2 );
    } 
    
    protected void finalize() {
      try {
        stream.close();
      } catch (Throwable t) {
      }
    } 
    
    public static void main(String[] args) throws Exception {
      Example example = new Example(); 
      example.work();
    }

    乍看之下,我们有缺陷的安全网示例似乎没有任何问题,在许多情况下,它可以正确执行。 但是,在正确的条件下,它将以意外的异常失败。 ( 精明的读者会注意到FileOutputStream已经具有内置的终结器,因此此示例是多余的;尽管如此,并非所有资源都有一个,所以此示例旨在作为简要说明

    异常1:表明过早完成的异常

    线程“主”中的异常java.io.IOException:流已关闭

    at java.io.FileOutputStream.writeBytes(Native Method)
    at java.io.FileOutputStream.write(FileOutputStream.java:325)
    at Example.work(Example.java:36)
    at Example.main(Example.java:47)

    此失败清楚地表明了终结器在执行工作方法期间以某种方式运行,但是出现了问题,如何以及为什么会发生这种情况?

    详细了解OpenJDK的HotSpot的机制将提供有关如何发生这种情况的见解。

    它如何发生-深入JVM内部

    在研究HotSpot的行为时,了解一些关键概念很有用。 在HotSpot下,如果可以从堆上的另一个对象,JNI句柄或从线程堆栈上的方法执行的正在使用的本地引用中访问对象,则将它们视为活动对象。

    由于HotSpot具有先进的即时编译器,因此确定是否使用本地引用非常复杂。 该编译器根据目标CPU架构以及包括活动负载模式在内的实时环境因素,将Java字节码转换为优化的本机指令。 由于生成的代码可能会有很大的不同,因此需要一种与垃圾收集器协调的有效且可靠的机制。

    在HotSpot下,此机制称为安全点。 当线程达到安全点时,垃圾回收器将有机会安全地操纵线程的状态,并确定活动的本地对象,因为应用程序代码的执行被暂时挂起。 只有程序执行中的某些点才可以成为安全点,其中最值得注意的是方法调用。

    在本机代码生成期间,JIT编译器会在每个潜在的安全点存储GC映射。 GC映射包含当时由JIT编译器视为活动对象的对象列表。 然后,垃圾收集器可以使用这些映射并准确确定哪些对象是本地可访问的,而无需了解其背后的本机代码。

    通过在上面的示例中,在示例work()方法的开头插入任意方法调用,例如yield(),可以将该时间点的GC映射与以后的方法调用中的映射进行比较,从而确定HotSpot何时确定对象引用符合收集条件。 让我们做更多的分析,看看是什么原因导致了上述异常。

    示例4:带有伪方法的安全网络终结器有缺陷,可以比较GC映射

    public void work() throws Exception {
      Thread.yield(); // Dummy method call, potential safe-point
      FileOutputStream stream = this .stream;
      stream.write( PART1 ); // Existing call already a potential safe-point
      stream.write( PART2 );
    }

    通过首先安装反汇编程序插件,然后使用适当的VM选项,可以在OpenJDK的印刷装配输出中检查GC映射。 仅当选择了要编译的方法时才会输出,因此需要附加参数来强制执行此操作。 最激进的优化由服务器编译器( C2 )执行,这使其成为此分析的理想选择。 请注意,在本地编译方法之前,此模式通常需要一万次调用。 将编译器阈值设置为1可使此情况立即发生。

    示例5:用于反汇编work()方法的JVM命令参数

    java —server XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Example.work -XX:CompileThreshold=1 -XX:-TieredCompilation Example

    图1:为简化而简化的HotSpot x86汇编语言反编译,并为每个指令块标注了相应的Java代码

    HotSpot遵循标准的x86-64调用约定。 调用work方法时,它将在执行该方法中的任何代码之前将“ this”对象引用的副本放入寄存器“ rsi”中。 第一条指令只是将放置在“ rsi”中的“ this”引用复制到工作寄存器“ rbp”中。

    mov rbp,rsi

    在第二条指令“调用”上,将调用伪方法Thread.yield,如前所述,该方法是潜在的安全点候选对象,因此C2已包括GC映射(在输出中标记为OopMap)。 此时,作为当前“ this”引用的“ rbp”的内容被标记为活动,因此无法收集该对象,因此此时无法完成。

    call   0x00007f0375037f60  ; OopMap{rbp=Oop off=36}
                               ;*invokestatic yield-> Thread.yield(); // New potential safe-point
    

    第三条指令“ mov”将“ stream”字段的内容复制到“ rbp”,覆盖先前存储的对“ this”的引用。 在HotSpot中,Java对象存储在内存的连续映射中,因此读取的字段只是添加到包含该字段的对象地址的偏移量。 在这种情况下,“ stream”位于距离“ this”开头16个字节的位置。

    mov rbp,QWORD PTR [rbp+0x10] ;*getfield stream-> FileOutputStream stream = this.stream; 

    下一组指令设置并对现在存储在“ rbp”寄存器中的“ stream”字段的内容执行write()调用。 “ test”和“ je”指令执行空指针检查,并在必要时抛出NullPointerException。 第一条“ mov”指令将常量“ PART1”(字节数组引用)复制到“ rdx”,从而为方法调用设置了参数。 当前包含“ stream”字段的“ rbp”寄存器被复制到“ rsi”,该寄存器遵循随后的write()调用的调用约定。

    test   rbp,rbp
    je     0x7f0375113ce0
    mov    rdx,0x7f02cdaea718  ;   {oop([B)}
    mov    rsi,rbp
    call   0x00007f0375037b60  ; OopMap{rbp=Oop off=64}
                               ;*invokevirtual write-> stream.write(PART1); 

    最终进行了write()调用,并且由于它是一个潜在的安全点,因此包含了另一个GC映射。 该图表示仅“ rbp”是可访问的。 由于“ rbp”被“ stream”覆盖,因此它不再包含“ this”,因此在此执行点不再认为“ this”是可访问的。 上图描述了整个work()方法代码中“ rbp”的状态。

    由于工作方法的“ this”引用是对该对象的唯一剩余引用,因此终结器现在可以与此write()调用并发执行,从而导致前面提到的失败的堆栈跟踪。 同样,与此对象关联的任何幻像引用都可以传递给相应的清理线程,从而导致相同的过早关闭。

    总而言之,该分析向我们展示了可以在对对象进行实时方法调用完成之前收集该对象。

    为什么会发生

    不幸的是,Java语言规范(12.6.1)明确允许这种行为:

    “可以设计程序的优化转换,以将可到达的对象数量减少到少于天真的被认为可到达的对象数量。 例如,Java编译器或代码生成器可以选择设置将不再用于为null的变量或参数,以使此类对象的存储可以更快地被回收。”

    更加不祥的是:

    “这种转换可能导致对finalize方法的调用比预期的要早。”

    尽管不直观,但急切清理的一般概念对性能很有帮助。 举个例子,持有一个对象将是一种浪费,该对象将不再被从事某种形式的长时间活动的方法之一所使用。 但是,此行为与终结器和幻影参考功能的交互作用适得其反且容易出错。

    缓解策略

    幸运的是,可以使用多种技术来防止这种错误行为。 但是,请记住,这些技术是微妙的,因此在使用和维护代码时要格外小心。

    全部同步策略

    此策略基于JLS(12.6.1)中的特殊规则:

    “如果对象的终结器可以导致该对象上的同步,则该对象必须处于活动状态,并且只要对其持有锁定,就认为该对象可以访问。”

    换句话说,如果终结器是同步的,则保证在所有其他挂起的同步方法调用完成之后才调用终结器。 这是最简单的方法,因为它所需要的只是向finalizer和所有可能与之冲突的方法(通常是所有方法)添加一个findizer。

    示例6:全部同步策略

    public synchronized void work() throws Exception {
      FileOutputStream stream = this .stream;
      stream.write( PART1 );
      stream.write( PART2 );
    } 
    
    protected synchronized void finalize() {
      try {
        stream.close();
      } catch (Throwable t) {
      }
    }
    public static void main(String[] args) throws Exception {
      Example example = new  Example(); 
      example.work();
    }

    这种方法最明显的缺点是,它序列化了对对象的所有访问,这排除了必须支持并发方法访问的任何类。 另一个缺点是同步的开销会严重影响性能。 在将实例固定在单个线程上较长时间的情况下,JVM可以在称为“锁偏置”的过程中“检出”该锁,从而消除了大部分成本。 但是,即使发生这种优化,由于Java内存模型的要求,可能仍需要内存围栏来同步CPU内核之间的状态,这通常会引入不必要的延迟。

    RWLock同步策略

    对于需要并发访问的对象,可以修改“全部同步”策略以支持并行work()方法执行。 这是通过使用ReadWriteLock和单独的清理线程来完成的。 work()方法在简短的同步下获取读锁,以确保终结器不运行,从而确保始终在写锁之前获取读锁。 单独的清理线程是必要的,因为由于先前列出的原因,清理任务一旦创建,就在写锁上阻塞,并且使JVM终结器执行线程停滞,就应该避免。

    示例7:与RWLock同步的策略

    private void work()  {
      ReentrantReadWriteLock lock = this .lock;
      try {
        synchronized  ( this ) { // Object lock prevents finalizer stall
          lock.readLock().lock();
        }
        stream.write( PART1 );
        stream.write( PART2 );
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      } finally {
        lock.readLock().unlock();
      }
    }
    protected synchronized void finalize() {
      // Delegate to another thread so we do not block the JVM finalizer thread
      REAPER .execute( new CleanupTask(lock));
    } 
    
    private static class CleanupTask implements Runnable {
      // Constructor and fields omitted for brevity
      public void run() {
        try {
          lock.writeLock().lock();
          safeClose(stream);
        } finally {
          lock.writeLock().unlock();
        }
      } 
    }

    这种方法的缺点是它很复杂并且在锁中获取了一个锁。 因为它的预期用途是支持并发工作方法执行,所以锁定偏差将无济于事。

    易变策略

    一种改进的方法是采取一些措施,使这些对象在work()方法期间保持活动状态。 天真的尝试是在工作方法的末尾读取一个字段。

    示例8:天真尝试,不能保证能正常工作

    private int dummy; 
    
    public void work() throws Exception {
      FileOutputStream stream = this.stream;
      stream.write( PART1 );
      stream.write( PART2 );
      // Not guaranteed to work
      int dummy = this .dummy;
    }
    

    由于优化器可以轻松地丢弃未使用的读取,因此无法保证此方法有效。 纠正此错误的“明智”尝试将利用另一条JLS规则,该规则指出所有字段写入必须对终结器可见。

    示例9:聪明的尝试,但也不能保证能正常工作

    private int counter;
    public void work() throws Exception {
      FileOutputStream stream = this .stream;
      stream.write(PART1);
      stream.write(PART2);
      // Not guaranteed to work either
      this .counter++;  
    }

    不幸的是,这种尝试也可能失败,因为优化器可以随意重排序指令,甚至可以取消写指令,因为它从未使用过。 示例10中的代码是等效的,因为该方法的结果仍然相同。

    示例10:优化器重定位指令

    public void work() throws Exception {
      FileOutputStream stream = this .stream;
      // An optimizer can relocate this instruction, triggering failure
      this .counter++; 
      stream.write( PART1 );
      stream.write( PART2 );
    }

    幸运的是,可以利用Java内存模型中的程序顺序规则来防止优化程序对指令重新排序。 JMM要求,在读取易失性字段或建立“先发生”关系的任何其他事件时,对易失性字段进行写入之前的所有内存效果对于所有其他执行线程都是可见的。

    通过将计数器更改为易失性,HotSpot不会在写调用上方对指令进行重新排序。 但是,从理论上讲,将来有一些优化程序可以确定不需要进行易失性写操作的存储效应,并且由于从不使用该字段,因此仍然有可能消除它。 可以通过将值发布到公共静态字段来安全地防止这种情况。 对于尚未加载的代码,公共静态字段的内容必须可见。 结合使用这些方法,可以得出一种有效的基于volatile的策略,该策略允许并发访问而无需任何形式的锁定。

    示例11:易变策略

    public static int STATIC_COUNTER = 0; 
    private volatile int counter = 0; 
    
    public void work() throws Exception {
      FileOutputStream stream = this .stream;
      stream.write( PART1 )
      stream.write( PART2 );
      this .counter++; // Volatile write prevents reordering 
    
    } 
    
    protected void finalize() {
      if (safeClose(stream)) {
        STATIC_COUNTER = counter; // Public static write prevents
                                  // possible elimination
      } 
    }

    尽管无法避免写操作,但每次写操作仍会发生内存隔离,理想情况下应避免这种情况。

    易失性+懒惰写策略

    对易失性策略进行小的修改可以降低写操作的成本,但仍然可以确保所需的排序效果。 使用AtomicIntegerFieldUpdater允许类执行延迟写入。 延迟写入使用便宜的存储区(称为存储区),这只能确保写入顺序。 x86和SPARC自然是有序的,因此在这些平台上,懒写实际上是免费的。 在某些平台上(例如ARM),这会花费很少的成本,但仍然比普通的易失性写入要少得多。

    示例12:易失性+惰性写入策略

    public static int STATIC_COUNTER = 0;
    private volatile int counter = 0;
    private static AtomicIntegerFieldUpdater UPDATER = …
    public void work() throws  Exception {
      FileOutputStream stream = this .stream;
      stream.write( PART1 );
      stream.write( PART2 );
      UPDATER .lazySet( this , counter + 1); // Volatile write prevents reordering
    } 
    
    protected void finalize() {
      if (safeClose(stream)) {
        STATIC_COUNTER = counter; // Public static write prevents
                                  // possible elimination
      } 
    }

    本机方法免疫

    JNI调用使对主机对象的引用保持活动状态,因此不需要特殊的策略。 但是,将本机方法与Java方法混合是很常见的,因此使用本机代码的类可能仍需要对所有Java方法采取适当的预防措施。

    需要改进

    这些策略虽然有效,但它们都笨拙,易碎,并且比干净的语言构造要昂贵得多。 .NET平台中已经存在这样的构造。 C#应用程序可以使用简单的GC.KeepAlive()调用,该调用告诉JIT编译器将传递的对象保留在该时间点之前。

    如果JDK要实现类似的构造,那么work()方法将如下所示:

    示例13:使用潜在的JDK改进的增强示例

    public void work() throws  Exception {
      FileOutputStream stream = this.stream;
      stream.write( PART1 );
      stream.write( PART2 );
      System.keepAlive( this );
    }
    

    这种方法没有不必要的开销。 该代码非常干净,将来的维护者也清楚其目的。 如有疑问,只需要检查一下keepAlive()方法的Javadoc。

    结论

    Java的终结器和幻像引用功能容易出错,通常应避免使用。 但是,这些功能有合理的用例,使用时,应采用本文中的一种策略来防止过早收集问题。

    资源的所有使用,无论是否可终结,都应始终使用try-with-resources或try / finally。 如果所有调用者都采取了这一重要步骤,则即使终结器损坏的对象也将正常运行。

    希望将来的JVM版本将实现keepAlive()构造,这将大大改善开发人员的体验,并在必要时减少此类错误的可能性。

    范例程式码

    GitHub上提供了每种策略的代码以及有缺陷的示例。

    翻译自: https://www.infoq.com/articles/Fatal-Flaw-Finalizers-Phantoms/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

    终结符和非终结符

    展开全文
  • int是终结符还是非终结符 重点 (Top highlight)How do you take your internet? I, for one, like it private and secure. 您如何上网? 我,一方面,喜欢它的私密性和安全性。 Unfortunately, it does not matter ...

    int是终结符还是非终结符

    重点 (Top highlight)

    How do you take your internet? I, for one, like it private and secure.

    您如何上网? 我,一方面,喜欢它的私密性和安全性。

    Unfortunately, it does not matter what any of us think. WebAssembly, an exciting new technology that lets browsers run native, low-level code may bring an end to privacy and security on the internet as we know it.

    不幸的是,我们每个人的想法都没有关系。 WebAssembly是一种令人兴奋的新技术,它使浏览器能够运行本机,低级代码,因此,众所周知,它可能会终结Internet上的隐私和安全性。

    什么是WebAssembly? (What Is WebAssembly?)

    WebAssembly, in short, is a runtime that lets browsers run native code. When I say native, I mean code written in low-level languages such as C, C++, Rust, etc.

    简而言之,WebAssembly是允许浏览器运行本机代码的运行时。 当我说本机时,我的意思是用低级语言(例如C,C ++, Rust等)编写的代码。

    Image for post

    This is a simplified diagram of the principles of WebAssembly. The compiler inputs code in an LLVM-compliant language and produces a binary .wasm file.

    这是WebAssembly原理的简化图。 编译器以兼容LLVM的语言输入代码,并生成一个二进制.wasm文件。

    This file is loaded and hooked up to the existing JS code by the JavaScript interop layer and the .wasm file is then executed by the WebAssembly runtime.

    该文件由JavaScript interop层加载并连接到现有JS代码,然后WebAssembly运行时执行.wasm文件。

    WebAssembly is now fully adopted and supported natively in all major browsers.

    现在,WebAssembly已在所有主要浏览器中完全采用并得到本机支持。

    WebAssembly可以做什么? (What Can WebAssembly Do?)

    Image for post
    PublicDomainPictures from PublicDomainPicturesPixabay Pixabay上发布

    WebAssembly offers exceptional performance. This means that your websites can work nearly as fast as regular software on your PC outside the browser.

    WebAssembly具有出色的性能。 这意味着您的网站可以在浏览器之外的PC上以与常规软件几乎相同的速度运行。

    I cannot stress enough just how revolutionary this is. It lets browsers run software and games that were not available before due to performance issues. It will make complex WebVR experiences possible. WebAssembly can make Chromebooks actually useful.

    我不能足够强调这是多么的革命。 它使浏览器可以运行由于性能问题而以前不可用的软件和游戏。 这将使复杂的WebVR体验成为可能。 WebAssembly可以使Chromebook真正有用。

    Image for post

    One application of WebAssembly you will find very interesting is Blazor by Microsoft. What Microsoft has done, basically, is port their whole .NET platform to WebAssembly and add a UI library on top.

    您会发现非常有趣的WebAssembly应用程序是Microsoft的Blazor 。 微软所做的基本上是将其整个.NET平台移植到WebAssembly并在顶部添加一个UI库。

    That means that if you are a .NET developer, you just learned web app development without really doing anything.

    这意味着,如果您是.NET开发人员,那么您只是学习Web应用程序开发而无需做任何事情。

    如何利用它? (How Can It Be Exploited?)

    There are lots of ways. They are made possible due to the unreadable nature of .wasm files. They can still be decompiled and analyzed, but that is not something that is as easy as prettifying JavaScript.

    有很多方法。 由于.wasm文件的不可读性,使它们成为可能。 仍然可以对它们进行反编译和分析,但这并不是像修饰JavaScript那样容易。

    A small tracking script may be included in one of the libraries that a website uses and no one will know about it. Thus, tracking protection, both first-party (Firefox) and third-party (plugins) will stop working.

    一个小的跟踪脚本可能包含在网站使用的一个库中,没有人会知道。 因此,无论是第一方(Firefox)还是第三方(插件)的跟踪保护都将停止工作。

    Secondly, the performance increase means that websites can now make better use of your hardware. cryptocurrency mining, for example. And again, it will be notoriously hard to block.

    其次,性能提高意味着网站现在可以更好地利用您的硬件。 例如, 加密货币挖矿 。 同样,这将很难被阻止。

    Lastly, there are security issues. With this much power and low-level access, certain attacks (brute-force) and vulnerabilities are becoming possible. We will just hope that the open-source community will keep up.

    最后,还有安全问题。 凭借如此强大的功能和低级别的访问权限,某些攻击(强力)和漏洞已成为可能。 我们只是希望开源社区能够跟上发展。

    结束语 (Closing Notes)

    So, the internet will definitely not be the same after the widespread adoption of WebAssembly.

    因此,在WebAssembly广泛采用之后,Internet肯定会不同。

    It is yet to see though, how nourishing or catastrophic the consequences will be. Thank you for reading and let me know in the comments what you think about WebAssembly!

    然而,后果尚有多大。 感谢您的阅读,并在评论中让我知道您对WebAssembly的看法!

    翻译自: https://medium.com/better-programming/webassembly-is-the-end-of-the-internet-as-we-know-it-9085a49cbc7b

    int是终结符还是非终结符

    展开全文
  • 为什么终结符只有综合属性?

    万次阅读 2020-11-22 10:16:45
    终结符的属性都是由词法分析器提供的,也叫做内在属性,被认为是综合属性,这是定义的特例。

    书本上关于综合属性与继承属性的定义是:

    1. 对关联产生式A-> α \alpha α的语义动作b:=f(c1,c2,c3,…,ck),如果b是A的某个属性,则称b是A的一个综合属性。从分析树的角度来看,计算综合属性是对父结点的属性赋值,所以是自底向上传递信息。
    2. 对关联产生式A-> α \alpha α的语义动作b:=f(c1,c2,c3,…,ck),如果b是产生式右部某个文法符号X的某个属性,则称b是文法符号X的一个继承属性。从分析树的角度来看,计算继承属性是对子结点的属性赋值,所以是自顶向下传递信息。

    那么从字面意思理解,b是A的某个属性,那么非终结符肯定有综合属性了;而终结符是不可能出现在产生式左边的,所以终结符是没有综合属性的;X在产生式右部,X可能是终结符,所以终结符是有继承属性的。

    从另一个角度理解:综合属性用于自底向上传递信息,一个结点的综合属性应该是由它的子结点决定,但是在语法树中终结符是没有子结点的,所以终结符不该有综合属性只能有继承属性。

    查遍了网上的所有资料,只有这么一句话:终结符只有综合属性,它由词法分析器提供。

    这样理解:两种属性的定义是从语法树的角度来说的。如果某个属性是它的子结点属性计算得到的,那么这个属性就是综合属性;如果某个属性是它的父结点或其兄弟结点计算得到的,那么这个属性就是继承属性特殊情况是终结符的属性都是由词法分析器提供的,也叫做内在属性,被认为是综合属性,这是定义的特例

    展开全文
  • 一、解释器模式1、基础概念解释器模式是对象的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释...(2)、终结符表达式TerminalExpress:实现抽象表...

    一、解释器模式

    1、基础概念

    解释器模式是对象的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的表达式。

    2、模式图解

    031c59a8fa460c56f07205d5f8f69ce5.png

    3、核心角色

    (1)、抽象表达式

    Express:声明具体表达式角色需要实现的抽象接口,该接口主要提供一个interpret()方法,称做解释操作。

    (2)、终结符表达式

    TerminalExpress:实现抽象表达式角色接口,主要是一个interpret()方法;每个终结符都有一个具体终结表达式与之相对应。比如解析c=a+b,a和b是终结符,解析a和b的解释器就是终结符表达式。

    (3)、非终结符表达式

    NotTerminalExpress:每一条规则都需要一个具体的非终结符表达式用来衔接,一般是指运算符或者逻辑判断,比如c=a+b,“+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。

    (4)、环境容器

    DataMap:一般是用来存放各个终结符所对应的具体值,比如c=a+b转换为c=1+2。这些信息需要一个存放环境。

    4、源代码实现

    类图结构

    be8d3996ee5d28142e10ec66888aa728.png

    源码实现

    public class C01_InScene {

    public static void main(String[] args) {

    DataMap dataMap = new DataMap();

    TerminalExpress terminalExpress1 = new TerminalExpress("num1");

    TerminalExpress terminalExpress2 = new TerminalExpress("num2");

    TerminalExpress terminalExpress3 = new TerminalExpress("num3");

    dataMap.putData(terminalExpress1, 1);

    dataMap.putData(terminalExpress2, 2);

    dataMap.putData(terminalExpress3, 3);

    // 1+2-3 = 0

    System.out.println(new Minus(

    new Add(terminalExpress1,terminalExpress2), terminalExpress3)

    .interpret(dataMap));

    }

    }

    // 解释器接口

    interface Express {

    Integer interpret(DataMap dataMap) ;

    }

    // 非终结符表达式

    abstract class NotTerminalExpress implements Express {

    Express express1,express2;

    public NotTerminalExpress(Express express1, Express express2){

    this.express1 = express1;

    this.express2 = express2;

    }

    }

    // 终结符表达式: 1+2 终结符: 1 和 2

    class TerminalExpress implements Express {

    public String field ;

    public TerminalExpress (String field){

    this.field = field ;

    }

    @Override

    public Integer interpret(DataMap dataMap) {

    return dataMap.getData(this);

    }

    }

    // 加法表达式

    class Add extends NotTerminalExpress {

    public Add (Express e1, Express e2) {

    super(e1, e2);

    }

    // 将两个表达式相减

    @Override

    public Integer interpret(DataMap context) {

    return this.express1.interpret(context) + this.express2.interpret(context);

    }

    }

    // 减法表达式

    class Minus extends NotTerminalExpress {

    public Minus (Express e1, Express e2) {

    super(e1, e2);

    }

    // 将两个表达式相减

    @Override

    public Integer interpret(DataMap context) {

    return this.express1.interpret(context) - this.express2.interpret(context);

    }

    }

    // 数据容器

    class DataMap {

    private Map dataMap = new HashMap<>() ;

    public void putData (Express key,Integer value){

    dataMap.put(key,value) ;

    }

    public Integer getData (Express key){

    return dataMap.get(key) ;

    }

    }

    二、Spring框架应用

    1、源码案例

    import org.springframework.expression.Expression;

    import org.springframework.expression.spel.standard.SpelExpressionParser;

    public class SpringTest {

    public static void main(String[] args) {

    SpelExpressionParser parser = new SpelExpressionParser () ;

    Expression expression = parser.parseExpression("(1+3-2)*3") ;

    Integer result = (Integer)expression.getValue() ;

    System.out.println("result="+result);

    }

    }

    2、代码分析

    (1)Expression结构

    表达式接口:具有不同的实现类。

    interface Expression

    class CompositeStringExpression implements Expression

    class LiteralExpression implements Expression

    class SpelExpression implements Expression

    核心方法:

    Object getValue() throws EvaluationException;

    (2)SpelExpressionParser结构

    SpelExpressionParser extends TemplateAwareExpressionParser

    TemplateAwareExpressionParser implements ExpressionParser

    interface ExpressionParser

    (3)ExpressionParser接口

    public interface ExpressionParser {

    Expression parseExpression(String var1) ;

    Expression parseExpression(String var1, ParserContext var2) ;

    }

    (4)Expression获取

    根据不同的条件获取不同的Expression对象。这里产生类的依赖关系。

    源码位置:TemplateAwareExpressionParser

    public Expression parseExpression(String expressionString,

    ParserContext context)

    throws ParseException {

    if (context == null) {

    context = NON_TEMPLATE_PARSER_CONTEXT;

    }

    return context.isTemplate() ?

    this.parseTemplate(expressionString, context) :

    this.doParseExpression(expressionString, context);

    }

    三、模式总结

    场景

    编译器、运算符表达式、正则表达式、机器人等。

    优点

    当有一个表达式或者语言需要解释执行,该场景下的内容可以考虑使用解释器模式,使程序具有良好的扩展性。

    缺点

    解释器模式会引起类膨胀,会导致程序执行和调试非常复杂,不容易理解。

    四、源代码地址

    GitHub·地址

    https://github.com/cicadasmile/model-arithmetic-parent

    GitEE·地址

    https://gitee.com/cicadasmile/model-arithmetic-parent

    展开全文
  • 编译原理-文法

    热门讨论 2014-04-30 12:33:09
    1、文法的定义 描述语言的语法结构的...终结符具有原子性,不可再分。非终结符,可以再分。 非终结符(程序)->终结符(语句) 终结符不可以单独出现在α中; 3、文法组成 ³ 文法G是一个四元组,可表示为G(V
  • 2-1 中间代码生成所依据的是( )。...终结符具有( )属性。(4分) 抽象 传递 综合 继承 2-4 以下说法正确的是( )。(4分) 语义规则中的属性有两种:综合属性与继承属性 终结符只有继承属性,...
  • 鹏哥牛逼逼 中间代码生成所依据的是( ) 四元式之间的联系是通过( )实现的 终结符具有( )属性。 以下说法正确的是( )。 四元式表示法的优点为( ) ...
  • 在初学编译原理时的第二章中就给了制导翻译程序的例子,在第五章中又再次提到,不过更加详细。...非终结符具有两种属性:综合属性和继承属性。分析树上的非终结符的综合属性是由产生式所关联的语义规则来定义的,
  • 上次由于自身错误坑了不少人,先说声抱歉哈,以后一定避免这样的事情发生。 中间代码生成所依据的是( )。...终结符具有( )属性。 综合 以下说法正确的是( )。 语义规则中的属性有两种:综合属性与继承属性 ...
  • 编译原理

    2020-02-16 11:30:47
    终结符具有综合属性 词法分析阶段不能识别四元式 词法分析器的输入是源程序 DFA构成:有穷字母表终止状态集合有限状态集合 编译过程包括词法分析 语法分析 语义分析和中间代码产生 中间代码优化 目标代码...
  • 所有的终结符都必须具有唯一的一种数据类型,意味着Parse()函数的第三个参数(记号的值)必须具有某种特定的统一的数据类型,通常我们用指针指向具有某种特定的统一的数据类型的记号的值来传递第三个参数。...
  • 2017计科01-08编译原理练习题一语义分析&中间代码生成 单选题 2-1中间代码生成所依据的是( C )。...2-3终结符具有(C )属性。 A.抽象 B.传递 C.综合 D.继承 2-4以下说法正确的是( A)。 A.语义规...
  • 愚蠢的终结者Build go build...-all示例./finalizers名称空间名称API版本终结符p-nf5gh creator-project-owner management.cattle.io/v3 ProjectRoleTemplateBinding [controller.cattle.io/mgmt-auth-prtb-controller]
  • Symbian的描述

    2010-06-18 10:25:00
    只不过与C++的字符串不一样,Symbian中的描述符都是用一个附加的整数描述其长度,而不是以’/0′做终结符。因此,描述符可以表达任意数据,字符串或者二进制串。描述符体系 打开任何一本关于Symbian介绍的书,...
  • Java修饰

    2018-02-05 20:03:23
    一 类的修饰 java中的文件结构由大到小为:一个工程,一个工程下可以有许多包,每个包中可以有许多类。 类的修饰分为 访问权限修饰 和 非访问权限修饰 (1)访问权限修饰: 1 public 公共类...
  • 在Java中,当一个对象变得不可到达时,垃圾回收器会回收与该对象相关联的存储空间。用try-finally块来回收其他的非内存...例如,用终结方法来关闭已经打开的文件是错误的,因为打开文件的描述是一种很有限的资源...
  • 这种分析方法首先要规定运算符之间(确切地说是终结符之间)的优先关系和结合性质,然后借助这种关系,比较相邻运算符的优先级来确定句型的可归约串并进行归约。 下面,以表达式的文法为例,说明采用这种分析法分析符号串 ...
  • 这一章的内容比较杂,基本上类似于基础部分的终结之章,回顾下之前学习的章节,1-5章介绍了结构性编程的基础知识,6-10章来介绍面向对象的内容,加上接下来11章对异常处理的延伸学习后,基本...
  • 浏览器--颠覆的终结

    千次阅读 2010-02-09 21:44:00
    看看我们的浏览器,它实际上也是一个shell,explorer或者bash管理的是磁盘文件和磁盘程序,而浏览器管理的是网络上的文件和程序,比如我们都知道的url,它的本意就是资源定位,和我们的C:/my/xx或者/home/zhaoya/...
  • sizeof终结解析

    千次阅读 2009-12-21 18:22:00
    百度百科c语言中判断数据类型长度 用法 sizeof(类型说明,数组名或表达式); 或 sizeof 变量名 1. 定义: sizeof是C/C++中的一个操作(operator)是也,简单的说其作用就是返回一个对象或者类型所占的...
  • 描述浅析

    2009-04-28 14:46:00
    一、不可修改的描述(基类TdesC)通过length()方法获取描述长度。实际上描述的长度都是由4个字节即32位来保存的,但实际上,其中的4位留做他用,只有28位用来保存描述的长度,这样的话一个描述的最大...
  • 例如:用终结方法去关闭已经打开的文件,这是一种严重错误,因为打开文件描述是一种很有限的资源,如果jvm延迟终结方法,导致多个文件处于打开的状态。2.终结方法不能保证一定会被执行。如果企图通过终结方法释放...
  • [b]1、作为语句终结符的换行符[/b] C和Java每个语句必须以分号结尾。在Ruby中,也可以用分号来终结语句,但这并不是必须的。只有当你试图在一行代码里面放置多条语句时,才须要使用分号分隔它们。...
  • 避免使用终结方法

    2017-08-29 21:37:27
    所谓的终结方法其实是指finalize()。终结方法finalizer通常是不可预测的,也是很危险的。一般情况下是不必要的。使用终结方法会导致行为不稳定,降低性能,以及可移植性问题。根据经验,应避免使用终结方法。 二、...
  • 只不过与C++的字符串不一样,Symbian中的描述符都是用一个附加的整数描述其长度,而不是以’/0′做终结符。因此,描述符可以表达任意数据,字符串或者二进制串。   描述符体系 打开任何一本关于...
  • 终结完整编

    千次阅读 2006-05-12 20:01:00
    终结完整编工具:WINNTAutoAttack自动攻击器(扫漏洞)SQLTOOLS(SA空口令连接器)RAMDIN影子3.0中文版(远程控制程序)RADMIN注册码:08US9A95I+lKa9nbOLXqv0V8xqdDvKGcNcTpN2wV11iSqOCVuA6A5KKZRHc5GVMIybWomK6rNwoj8mYy8...
  • 只不过与C++的字符串不一样,Symbian中的描述符都是用一个附加的整数描述其长度,而不是以/0做终结符。因此,描述符可以表达任意数据,字符串或者二进制串。描述符体系打开任何一本关于Symbian介绍的书,都可以看到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,922
精华内容 5,568
关键字:

终结符具有