【问题标题】:How come the same static field has two different ids in dalvik?为什么同一个静态字段在dalvik中有两个不同的id?
【发布时间】:2021-09-20 19:07:39
【问题描述】:

我正在编写自己的玩具 Dalvik VM,但我似乎无法弄清楚 dalvik 如何处理继承的静态字段。

考虑以下java代码:

class Parent { static int parent_int = 10; }
class MyCode extends Parent {
    public static void main(String[] args){
        System.out.println(parent_int + 1);
    }
}

当使用 javac 和 java 编译和运行时,它会向控制台打印11,正如人们所期望的那样。然而,当它被编译成 dalvik 时,parent_int 的值被转换成一个sget 语句来得到field@0000,而在Parent 的<clinit> 方法中parent_int 的字段id 是field@0001 .

在我的 Dalvik VM 实现中,这成为一个问题,因为 field@0000 未初始化,即使 Parent 类和 field@0001 已经初始化。

Dalvik VM 如何处理这个问题?它怎么知道它们是相关的,并且应该被认为是相同的?为什么它们一开始就变成了两个不同的领域,而它们也可以成为一个领域?

【问题讨论】:

  • 从技术上讲,静态成员从不“继承”;它们很容易访问。
  • 我想你可能在这里遗漏了一些东西。根据source.android.com/devices/tech/dalvik/dex-format,字段id是指包含定义者id、类型id和名称id的描述符。在您的示例中,子类对字段的引用将父类作为定义者。因此,在 Dalv VM 规范的实现中,您需要针对父类解析名称(“parent_int”),以找到 sget 正在操作的字段。或者换一种说法,“parent_id”的字段id在父类和子类中会有所不同。
  • 但关键是您正在实现 Java 语义。如果您的 Dalvik 实现与经典 Java 实现的结果不同……那么您很可能误解了 Dalvik 规范。
  • 并不是不在乎。只是子项中的 id 描述了父项中的一个字段。所以实现需要将其解析为父类,并查看父类(运行时)类级字段向量中的实际字段。因为那是存储实际字段值的地方。在父类中......不是子类。正如 Chrylis 所说:静态字段永远不会被继承。
  • 不,这样假设是不安全的。子类中声明的静态字段可以与父类中声明的静态字段同名。但它们是不同的领域。 (如果要正确实现 Dalvlik 规范,则需要了解阴影、隐藏和覆盖之间的区别;请参阅dzone.com/articles/variable-shadowing-and-hiding-in-java)

标签: java android jvm dalvik


【解决方案1】:

简短回答:field@0000 和 field@0001 实际上是不同的引用。

Longer:让我们一步一步来:

反编译:

[tmp]$ dextra  -j -d -D classes.dex
/* 0 */ class   Parent  {
 /** 1 Static Fields (not printed - use -f to print) **/
 /** 2 Direct Methods  **/
 static  void <clinit> () // Class Constructor
    {
    /* # of Registers: 1 */
    /* # of Instructions: 5 */
    /* 0000: Op 1300 0a00       const/16 v0, 0xa */
    v0 = 10; 
    /* 0002: Op 6700 0100       sput v0, ?@1 */
    Parent.parent_int = v0; // (Field@1)
    /* 0004: Op 0e00            return-void  */
    return;

    } // end <clinit>
  void <init> () // Constructor
    {
    /* # of Registers: 1 */
    /* # of Instructions: 4 */
    /* 0000: Op 7010 0500 0000  invoke-direct { v0 } */
    result = java.lang.Object.<init>(v0); // (Method@5(v0))
    /* 0003: Op 0e00            return-void  */
    return;

    } // end <init>
    }  // end class Parent
/* 1 */ class   MyCode extends Parent   {    /** 2 Direct Methods  **/
      void <init> () // Constructor
        {
        /* # of Registers: 1 */
        /* # of Instructions: 4 */
        /* 0000: Op 7010 0300 0000  invoke-direct { v0 } */
        result = Parent.<init>(v0); // (Method@3(v0))
        /* 0003: Op 0e00            return-void  */
        return;

        } // end <init>
     public static  void main (java.lang.String[])
        {
        /* # of Registers: 2 */
        /* # of Instructions: 19 */
        /* 0000: Op 6201 0200       sget-object v1, ?@2 */
        v1 = java.lang.System.out; // (Field@2)
        /* 0002: Op 6000 0000       sget v0, ?@0 */
        v0 = MyCode.parent_int; // (Field@0)
        /* 0004: Op d800 0001       add-int/lit8 v0, 1 */
        v0 +=  1;
        /* 0006: Op 6e20 0400 0100  invoke-virtual { v1, v0 } */
        result = java.io.PrintStream.println(java.lang.System.out, MyCode.parent_int); // (Method@4(v1, v0))
        /* 0009: Op 6201 0200       sget-object v1, ?@2 */
        v1 = java.lang.System.out; // (Field@2)
        /* 000b: Op 6000 0100       sget v0, ?@1 */
        v0 = Parent.parent_int; // (Field@1)
        /* 000d: Op d800 0001       add-int/lit8 v0, 1 */
        v0 +=  1;
        /* 000f: Op 6e20 0400 0100  invoke-virtual { v1, v0 } */
        result = java.io.PrintStream.println(java.lang.System.out, Parent.parent_int); // (Method@4(v1, v0))
        /* 0012: Op 0e00            return-void  */
        return;

        } // end main
    }  // end class MyCode

(这是您的代码,但在您的代码之后添加另一个“System.out.println(Parent.parent_int + 1);”)

现在,正如您所说的那样,父引用 field1,而 main 查看 @0。如果我们显示字段:

[tmp]$ dextra  -F classes.dex
         Field (0) Definer: LMyCode; Name: parent_int type: I Flags: 0x0 
         Field (1) Definer: LParent; Name: parent_int type: I Flags: 0x0 
         Field (2) Definer: Ljava/lang/System; Name: out type: Ljava/io/PrintStream; Flags: 0x0

我们看到字段 1 属于 Parent,字段 0 属于 MyCode。由于它们都是静态的,它们由每个类单独拥有:在 Dalvik 对 MyCode 的内部初始化(我们没有看到,因为这是在运行时级别处理的,现在通过从 .ART 中克隆影子)时,字段引用“static_int”被复制过来(从孩子的影子,而不是父母的影子)——但是对父字段的进一步引用(比如我添加的 system.out.println)将寻址字段 1,而不是 0。


编辑:请注意,从类的角度来看,只有字段,属于父级:

类 0:父文件:MyCode.java 1 静态字段 2 直接方法 第 1 类:我的代码 扩展 LParent;文件:MyCode.java 2 直接方法

因此“parent_int”在很大程度上归父类所有。


请注意,您总是在这个或那个对象/类的上下文中引用该字段,所以如果不是为了那个优化。所以 - 严格来说,Dalvik 可以折叠两个字段(相同) - 但不会这样做,因为拥有这个看似“重复”的字段的成本并不高,值得快速访问定义器。

【讨论】:

  • 它(字段)也不会在实例化时被复制。在使用这 2 个类的整个应用程序中,只有一个 parent_int 字段/变量。它在任何给定时间都有一个值。并且子类main 方法中的代码正在更新它。
  • 完全同意,并进行了编辑以确保措辞清晰:(来自孩子的影子,而不是父母的参考)。并不意味着将父类的字段本身复制到子类。还表明 parent_int 属于.. 好吧,父母。但是,DEX 字段表(字段 0 和字段 1)中有两个“parent_int”引用指向同一个实际字段,如上所示,这就是问题所在。
猜你喜欢
  • 2022-11-30
  • 2013-06-12
  • 1970-01-01
  • 2015-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多