Как получилось, что одно и то же статическое поле имеет два разных идентификатора в dalvik?

#java #android #jvm #dalvik

Вопрос:

Я пишу свою собственную игрушечную виртуальную машину Dalvik и, похоже, не могу понять, как 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 , в то время как в <clinit> методе Parent идентификатор поля parent_int is field@0001 .

В моей реализации виртуальной машины Dalvik это становится проблемой, так field@0000 как не инициализируется, хотя Parent класс и field@0001 был.

Как виртуальная машина Dalvik справляется с этим? Откуда он знает, что они связаны и должны рассматриваться как одно и то же? И почему они вообще были превращены в две разные области, когда они с таким же успехом могли бы быть одним целым?

Комментарии:

1. Технически статические члены никогда не «наследуются»; они просто доступны.

2. Я думаю, что вы, возможно, что-то упускаете здесь. Согласно source.android.com/devices/tech/dalvik/dex-format , идентификатор поля относится к дескриптору, содержащему идентификатор определителя, идентификатор типа и идентификатор имени. В вашем примере ссылка на поле из дочернего класса будет содержать родительский класс в качестве определителя. Таким образом, в реализации спецификации виртуальной машины Dalvic вам необходимо сопоставить имя («parent_int») с родительским классом, чтобы найти поле, над которым sget он работает. Или, другими словами, идентификаторы полей для «parent_id» будут отличаться в родительском и дочернем классах.

3. Но дело в том, что вы реализуете семантику Java. Если ваша реализация Dalvik дает другие результаты, чем классическая реализация Java … тогда вы, скорее всего, неправильно поняли спецификацию Dalvik.

4. Это не значит, что ему все равно. Просто идентификатор в дочернем элементе описывает поле в родительском. Таким образом, реализация должна разрешить его в родительский класс и посмотреть на фактическое поле в векторе полей уровня класса родительских классов (среда выполнения). Потому что именно там хранятся фактические значения полей. В родительском классе … не в детском классе. Как утверждает Крилис: статические поля НИКОГДА не наследуются.

5. И нет, было бы небезопасно предполагать это. Статическое поле, объявленное в дочернем классе, может иметь то же имя, что и статическое поле, объявленное в родительском классе. Но это разные области. (Если вы собираетесь правильно реализовать спецификацию Dalvlik, вам необходимо понять различия между затенением, скрытием и переопределением; см. dzone.com/articles/variable-shadowing-and-hiding-in-java )

Ответ №1:

Краткий ответ: Поле@0000 и поле@0001 на самом деле являются разными ссылками.

Дольше: Давайте будем делать это шаг за шагом:

Декомпиляция:

 [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);» после вашего)

Теперь, как вы правильно сказали, поле родительских ссылок 1, в то время как основное смотрит на @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 принадлежит Родителю, а поле 0-MyCode. Поскольку они оба статичны, они принадлежат каждому классу по отдельности: при внутренней инициализации Dalvik MyCode (которую мы не видим, поскольку это обрабатывается на уровне времени выполнения, в настоящее время путем клонирования тени из .ART) ссылка на поле «static_int» копируется (из тени дочернего, а не родительского поля), но дальнейшие ссылки на родительское поле (например, system.out.print, которую я добавил) будут адресованы полю 1, а не 0.


ПРАВКА: Обратите внимание, что с точки зрения класса существует только поле, принадлежащее родительскому:

Класс 0: Родительский файл: MyCode.java 1 статические поля 2 Прямые методы Класс 1: MyCode расширяет LParent; Файл: MyCode.java 2 Прямых Метода

так что «parent_int» в значительной степени принадлежит родительскому классу.


Обратите внимание, что вы всегда ссылаетесь на поле в контексте того или иного объекта/класса, поэтому, если бы не эта оптимизация. Итак, строго говоря, Dalvik мог свернуть оба поля (будучи идентичными), но не будет этого делать, поскольку стоимость наличия этого, казалось бы, «дублирующего» поля невелика и стоит быстрого доступа к определителю.

Комментарии:

1. Оно (поле) также не копируется при создании экземпляра. Во всем приложении есть одно и только одно parent_int поле / переменная, которая использует эти 2 класса. Он имеет одно значение в любой момент времени. И код в методе дочерних классов main обновляет его.

2. Полностью согласен и отредактирован, чтобы убедиться, что формулировка ясна: (ссылка из тени ребенка, а не родителя). Это не означало, что само поле родительского класса было скопировано в дочерний. Также показал, что parent_int принадлежит.. ну, родителю. Однако есть ДВЕ ссылки «parent_int» из таблицы полей DEX (поле 0 и поле 1) на одно и то же фактическое поле, как показано выше, о чем и был задан вопрос.