【问题标题】:Where is the description of Constant Folding in the Java Language Specification, Java SE 11 Edition (JLS SE 11)?Java 语言规范 Java SE 11 版 (JLS SE 11) 中对常量折叠的描述在哪里?
【发布时间】:2020-08-10 23:35:00
【问题描述】:

据我所知,Java 在编译时通过常量折叠来处理常量变量§4.12.4。我已经尽力了,但我无法从 JLS 中找到它的描述。谁能告诉我在哪里可以找到关于 Java 11 的常量折叠过程的官方描述?

【问题讨论】:

    标签: java java-11 jls compile-time-constant constantfolding


    【解决方案1】:

    规范没有使用术语常量折叠。

    它有Constant Expressions的定义

    常量表达式是表示原始类型值或String的表达式,它不会突然完成并且仅使用以下内容组成:

    […]

    String 类型的常量表达式始终是“内部”的,以便使用 String.intern 方法共享唯一实例。

    常量表达式始终被视为 FP-strict (§15.4),即使它出现在非常量表达式不会被视为 FP-strict 的上下文中。

    常量表达式在 switch 语句 (§14.11) 中用作 case 标签,在赋值上下文 (§5.2) 和类或接口的初始化 (§12.4.2) 中具有特殊意义)。它们还可以控制whiledofor 语句正常完成的能力(§14.21),以及带有数字操作数的条件运算符? : 的类型。 p>

    最后一部分已经指出了常量表达式的预先计算是强制性的。对于case 标签,编译器需要报告重复项,因此,它必须在编译时计算值。计算循环时,必须计算常量布尔表达式以确定代码的可达性。

    同样,初始化器需要预先计算来确定正确性。例如。 short s = 'a' * 2; 是正确的声明,但 short s = Short.MAX_VALUE + 1; 不是。

    常量表达式的一个众所周知的用例是常量变量的初始化器。读取常量变量时​​,会使用常量值代替读取变量,对比问答“Does the JLS require inlining of final String constants?”

    但这并不意味着“不断折叠”是强制性的。理论上,一致的实现仍然可以在使用变量的每个地方执行变量初始化程序中写入的常量表达式的计算。在实践中,字节码格式会导致恒定的折叠行为。 ConstantValue 属性用于记录字节码中常量变量的值,只能保存预先计算的值。在针对已编译的类文件进行编译时,编译器无法使用常量变量的原始表达式。它只能使用预先计算的值。

    同样,compiling a switch instruction 通常使用tableswitchlookupswitch 指令来完成,两者都需要为case 标签预先计算int 值。编译器必须花很长时间才能实现不同的策略。

    另外,the compiled format for annotation values 只能保存预先计算好的表达式。

    【讨论】:

      【解决方案2】:

      Java 语言规范定义了语言的语义;常量折叠是一种编译器优化,它不会改变 Java 程序的行为,因此在 JLS 中没有指定,也不需要。允许 Java 的实现不这样做,或者在某些情况下不这样做,但在其他情况下不这样做,只要编译后的程序按照 JLS 所说的去做。

      也就是说,JLS 确实以这样一种方式定义了语言语义,即在更多情况下允许常量折叠而不改变程序的行为。最相关的段落可能是您在§14.2.4 中引用的内容:

      常量变量是原始类型或 String 类型的最终变量,它使用常量表达式(第 15.28 节)进行初始化。变量是否为常量变量可能会对类初始化(第 12.4.1 节)、二进制兼容性(第 13.1 节)、可达性(第 14.21 节)和明确赋值(第 16.1.1 节)产生影响。

      关于类初始化、二进制兼容性、可达性和明确赋值的引用部分专门定义了常量变量的语义,与其他变量不同;具体来说,它们定义的行为是您对折叠常量的编译器所期望的行为。这允许那些实施规范的人在不过度限制他们如何做的情况下进行优化。

      【讨论】:

      • 现在我明白为什么JLS没有了,不知道其他Java官方文档是否有更多关于常量折叠的信息?
      【解决方案3】:

      字段流程链接自问题中链接的页面:https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1

      对作为常量变量的字段(第 4.12.4 节)的引用必须是 在编译时解析为常量表示的值 V 变量的初始化器。

      如果这样的字段是静态的,则不应对该字段的引用 存在于二进制文件的代码中,包括类或接口 宣布了该领域。这样一个字段必须总是看起来 初始化(§12.4.2);字段的默认初始值(如果 不同于 V) 绝对不能被观察到。

      如果这样的字段是非静态的,则不应对该字段的引用 存在于二进制文件的代码中,类中除外 包含该字段。 (它将是一个类而不是一个接口, 因为接口只有静态字段。)类应该有代码 在实例创建期间将字段的值设置为 V (§12.5)。

      【讨论】:

      • 这允许对引用常量变量的名称进行常量折叠,但它并不考虑常量折叠的所有情况,例如在编译时将2 * 3替换为6
      • @kaya3 你说得对,我只想到常量字段。
      猜你喜欢
      • 2020-12-19
      • 1970-01-01
      • 1970-01-01
      • 2023-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      相关资源
      最近更新 更多