33.5 std::map and std::set

二叉树是另一个基本的数据结构。它是一个树,但是每个节点最多包含两个指向其他节点的指针。每个节点包含键和/或值。 二叉树在键值字典的实现中是常用数据结构。 二叉树至少包含三个重要的属性: 所有的键的存储是排序的。 任何类型的键都容易存储。二叉树并不知道键的类型,因此需要键比较算法。 查找键的速度相比于链表和数组要快。 下面是一个非常简单的例子,我们在二叉树中存储下面这些数据0,1,2,3,5,6,9,10,11,12,20,99,100,101,107,1001,1010.

所有小于根节点键的键存储在树的左侧,所有大于根节点键的键存储在树的右侧。 因此,查找算法就很直接,当要查找的值小于当前节点的键的值,则查找左侧;如果大于当前节点的键的值,则查找右侧;当值相等时,则停止查找。这就是为什么通过键比较函数,算法可以查找数值、文本串等。 所有键的值都是唯一的。 如果这样,为了在n个键的平衡二叉树中查找一个键将需要log2 n步。1000个键需要10步,10000个键需要13步。但是这要求树总是平衡的,即键应该均匀的分布在树的不同层里。插入和删除操作需要保持树的平衡状态。 有一些流行的平衡算法可用,包括AVL树和红黑树。红黑树通过给节点增加一个color值来简化平衡过程,因此每个节点可能是红或者黑。 GCC和MSVC中的std::map和std::set模板的实现均采用了红黑树。 std::set只包含键。std::map是set的扩展,它在每个节点中包含一个值。

** MSVC **

  1. #include <map>
  2. #include <set>
  3. #include <string>
  4. #include <iostream>
  5. // struct is not packed!
  6. struct tree_node
  7. {
  8. struct tree_node *Left;
  9. struct tree_node *Parent;
  10. struct tree_node *Right;
  11. char Color; // 0 - Red, 1 - Black
  12. char Isnil;
  13. //std::pair Myval;
  14. unsigned int first; // called Myval in std::set
  15. const char *second; // not present in std::set
  16. };
  17. struct tree_struct
  18. {
  19. struct tree_node *Myhead;
  20. size_t Mysize;
  21. };
  22. void dump_tree_node (struct tree_node *n, bool is_set, bool traverse)
  23. {
  24. printf ("ptr=0x%p Left=0x%p Parent=0x%p Right=0x%p Color=%d Isnil=%d\n", n, n->Left, n->Parent, n->Right, n->Color, n->Isnil);
  25. if (n->Isnil==0)
  26. {
  27. if (is_set)
  28. printf ("first=%d\n", n->first);
  29. else
  30. printf ("first=%d second=[%s]\n", n->first, n->second);
  31. }
  32. if (traverse)
  33. {
  34. if (n->Isnil==1)
  35. dump_tree_node (n->Parent, is_set, true);
  36. else
  37. {
  38. if (n->Left->Isnil==0)
  39. dump_tree_node (n->Left, is_set, true);
  40. if (n->Right->Isnil==0)
  41. dump_tree_node (n->Right, is_set, true);
  42. };
  43. };
  44. };
  45. const char* ALOT_OF_TABS="\t\t\t\t\t\t\t\t\t\t\t";
  46. void dump_as_tree (int tabs, struct tree_node *n, bool is_set)
  47. {
  48. if (is_set)
  49. printf ("%d\n", n->first);
  50. else
  51. printf ("%d [%s]\n", n->first, n->second);
  52. if (n->Left->Isnil==0)
  53. {
  54. printf ("%.*sL-------", tabs, ALOT_OF_TABS);
  55. dump_as_tree (tabs+1, n->Left, is_set);
  56. };
  57. if (n->Right->Isnil==0)
  58. {
  59. printf ("%.*sR-------", tabs, ALOT_OF_TABS);
  60. dump_as_tree (tabs+1, n->Right, is_set);
  61. };
  62. };
  63. void dump_map_and_set(struct tree_struct *m, bool is_set)
  64. {
  65. printf ("ptr=0x%p, Myhead=0x%p, Mysize=%d\n", m, m->Myhead, m->Mysize);
  66. dump_tree_node (m->Myhead, is_set, true);
  67. printf ("As a tree:\n");
  68. printf ("root----");
  69. dump_as_tree (1, m->Myhead->Parent, is_set);
  70. };
  71. int main()
  72. {
  73. // map
  74. std::map<int, const char*> m;
  75. m[10]="ten";
  76. m[20]="twenty";
  77. m[3]="three";
  78. m[101]="one hundred one";
  79. m[100]="one hundred";
  80. m[12]="twelve";
  81. m[107]="one hundred seven";
  82. m[0]="zero";
  83. m[1]="one";
  84. m[6]="six";
  85. m[99]="ninety-nine";
  86. m[5]="five";
  87. m[11]="eleven";
  88. m[1001]="one thousand one";
  89. m[1010]="one thousand ten";
  90. m[2]="two";
  91. m[9]="nine";
  92. printf ("dumping m as map:\n");
  93. dump_map_and_set ((struct tree_struct *)(void*)&m, false);
  94. std::map<int, const char*>::iterator it1=m.begin();
  95. printf ("m.begin():\n");
  96. dump_tree_node ((struct tree_node *)*(void**)&it1, false, false);
  97. it1=m.end();
  98. printf ("m.end():\n");
  99. dump_tree_node ((struct tree_node *)*(void**)&it1, false, false);
  100. // set
  101. std::set<int> s;
  102. s.insert(123);
  103. s.insert(456);
  104. s.insert(11);
  105. s.insert(12);
  106. s.insert(100);
  107. s.insert(1001);
  108. printf ("dumping s as set:\n");
  109. dump_map_and_set ((struct tree_struct *)(void*)&s, true);
  110. std::set<int>::iterator it2=s.begin();
  111. printf ("s.begin():\n");
  112. dump_tree_node ((struct tree_node *)*(void**)&it2, true, false);
  113. it2=s.end();
  114. printf ("s.end():\n");
  115. dump_tree_node ((struct tree_node *)*(void**)&it2, true, false);
  116. };

Listing 34.13: MSVC 2012

  1. dumping m as map:
  2. ptr=0x0020FE04, Myhead=0x005BB3A0, Mysize=17
  3. ptr=0x005BB3A0 Left=0x005BB4A0 Parent=0x005BB3C0 Right=0x005BB580 Color=1 Isnil=1
  4. ptr=0x005BB3C0 Left=0x005BB4C0 Parent=0x005BB3A0 Right=0x005BB440 Color=1 Isnil=0
  5. first=10 second=[ten]
  6. ptr=0x005BB4C0 Left=0x005BB4A0 Parent=0x005BB3C0 Right=0x005BB520 Color=1 Isnil=0
  7. first=1 second=[one]
  8. ptr=0x005BB4A0 Left=0x005BB3A0 Parent=0x005BB4C0 Right=0x005BB3A0 Color=1 Isnil=0
  9. first=0 second=[zero]
  10. ptr=0x005BB520 Left=0x005BB400 Parent=0x005BB4C0 Right=0x005BB4E0 Color=0 Isnil=0
  11. first=5 second=[five]
  12. ptr=0x005BB400 Left=0x005BB5A0 Parent=0x005BB520 Right=0x005BB3A0 Color=1 Isnil=0
  13. first=3 second=[three]
  14. ptr=0x005BB5A0 Left=0x005BB3A0 Parent=0x005BB400 Right=0x005BB3A0 Color=0 Isnil=0
  15. first=2 second=[two]
  16. ptr=0x005BB4E0 Left=0x005BB3A0 Parent=0x005BB520 Right=0x005BB5C0 Color=1 Isnil=0
  17. first=6 second=[six]
  18. ptr=0x005BB5C0 Left=0x005BB3A0 Parent=0x005BB4E0 Right=0x005BB3A0 Color=0 Isnil=0
  19. first=9 second=[nine]
  20. ptr=0x005BB440 Left=0x005BB3E0 Parent=0x005BB3C0 Right=0x005BB480 Color=1 Isnil=0
  21. first=100 second=[one hundred]
  22. ptr=0x005BB3E0 Left=0x005BB460 Parent=0x005BB440 Right=0x005BB500 Color=0 Isnil=0
  23. first=20 second=[twenty]
  24. ptr=0x005BB460 Left=0x005BB540 Parent=0x005BB3E0 Right=0x005BB3A0 Color=1 Isnil=0
  25. first=12 second=[twelve]
  26. ptr=0x005BB540 Left=0x005BB3A0 Parent=0x005BB460 Right=0x005BB3A0 Color=0 Isnil=0
  27. first=11 second=[eleven]
  28. ptr=0x005BB500 Left=0x005BB3A0 Parent=0x005BB3E0 Right=0x005BB3A0 Color=1 Isnil=0
  29. first=99 second=[ninety-nine]
  30. ptr=0x005BB480 Left=0x005BB420 Parent=0x005BB440 Right=0x005BB560 Color=0 Isnil=0
  31. first=107 second=[one hundred seven]
  32. ptr=0x005BB420 Left=0x005BB3A0 Parent=0x005BB480 Right=0x005BB3A0 Color=1 Isnil=0
  33. first=101 second=[one hundred one]
  34. ptr=0x005BB560 Left=0x005BB3A0 Parent=0x005BB480 Right=0x005BB580 Color=1 Isnil=0
  35. first=1001 second=[one thousand one]
  36. ptr=0x005BB580 Left=0x005BB3A0 Parent=0x005BB560 Right=0x005BB3A0 Color=0 Isnil=0
  37. first=1010 second=[one thousand ten]
  38. As a tree:
  39. root----10 [ten]
  40. L-------1 [one]
  41. L-------0 [zero]
  42. R-------5 [five]
  43. L-------3 [three]
  44. L-------2 [two]
  45. R-------6 [six]
  46. R-------9 [nine]
  47. R-------100 [one hundred]
  48. L-------20 [twenty]
  49. L-------12 [twelve]
  50. L-------11 [eleven]
  51. R-------99 [ninety-nine]
  52. R-------107 [one hundred seven]
  53. L-------101 [one hundred one]
  54. R-------1001 [one thousand one]
  55. R-------1010 [one thousand ten]
  56. m.begin():
  57. ptr=0x005BB4A0 Left=0x005BB3A0 Parent=0x005BB4C0 Right=0x005BB3A0 Color=1 Isnil=0
  58. first=0 second=[zero]
  59. m.end():
  60. ptr=0x005BB3A0 Left=0x005BB4A0 Parent=0x005BB3C0 Right=0x005BB580 Color=1 Isnil=1
  61. dumping s as set:
  62. ptr=0x0020FDFC, Myhead=0x005BB5E0, Mysize=6
  63. ptr=0x005BB5E0 Left=0x005BB640 Parent=0x005BB600 Right=0x005BB6A0 Color=1 Isnil=1
  64. ptr=0x005BB600 Left=0x005BB660 Parent=0x005BB5E0 Right=0x005BB620 Color=1 Isnil=0
  65. first=123
  66. ptr=0x005BB660 Left=0x005BB640 Parent=0x005BB600 Right=0x005BB680 Color=1 Isnil=0
  67. first=12
  68. ptr=0x005BB640 Left=0x005BB5E0 Parent=0x005BB660 Right=0x005BB5E0 Color=0 Isnil=0
  69. first=11
  70. ptr=0x005BB680 Left=0x005BB5E0 Parent=0x005BB660 Right=0x005BB5E0 Color=0 Isnil=0
  71. first=100
  72. ptr=0x005BB620 Left=0x005BB5E0 Parent=0x005BB600 Right=0x005BB6A0 Color=1 Isnil=0
  73. first=456
  74. ptr=0x005BB6A0 Left=0x005BB5E0 Parent=0x005BB620 Right=0x005BB5E0 Color=0 Isnil=0
  75. first=1001
  76. As a tree:
  77. root----123
  78. L-------12
  79. L-------11
  80. R-------100
  81. R-------456
  82. R-------1001
  83. s.begin():
  84. ptr=0x005BB640 Left=0x005BB5E0 Parent=0x005BB660 Right=0x005BB5E0 Color=0 Isnil=0
  85. first=11
  86. s.end():
  87. ptr=0x005BB5E0 Left=0x005BB640 Parent=0x005BB600 Right=0x005BB6A0 Color=1 Isnil=1

结构体没有打包,因此所有的char类型都占用4个字节。 对于std::map,first和second变量可以看做是一个std::pair型变量。而在std::set中,std::pair只有一个变量。 当前树的节点个数总被保存着,这与MSVC中的std::list实现方式一样。 和std::list一样,迭代器只是指向节点的指针。.begin()迭代器指向最小的键。.begin()指针没有存储在某个位置(和list一样),树中最小的key总是可以找到。当节点有前一个和后一个节点时,- -和++操作会将迭代器移动到前一个或者后一个节点。关于这些操作的算法在文献七中进行了描述。 .end()迭代器指向根节点,它的Isnil的值为1,即这个节点没有键和值。

GCC

  1. #include <stdio.h>
  2. #include <map>
  3. #include <set>
  4. #include <string>
  5. #include <iostream>
  6. struct map_pair
  7. {
  8. int key;
  9. const char *value;
  10. };
  11. struct tree_node
  12. {
  13. int M_color; // 0 - Red, 1 - Black
  14. struct tree_node *M_parent;
  15. struct tree_node *M_left;
  16. struct tree_node *M_right;
  17. };
  18. struct tree_struct
  19. {
  20. int M_key_compare;
  21. struct tree_node M_header;
  22. size_t M_node_count;
  23. };
  24. void dump_tree_node (struct tree_node *n, bool is_set, bool traverse, bool dump_keys_and_values)
  25. {
  26. printf ("ptr=0x%p M_left=0x%p M_parent=0x%p M_right=0x%p M_color=%d\n", n, n->M_left, n->M_parent, n->M_right, n->M_color);
  27. void *point_after_struct=((char*)n)+sizeof(struct tree_node);
  28. if (dump_keys_and_values)
  29. {
  30. if (is_set)
  31. printf ("key=%d\n", *(int*)point_after_struct);
  32. else
  33. {
  34. struct map_pair *p=(struct map_pair *)point_after_struct;
  35. printf ("key=%d value=[%s]\n", p->key, p->value);
  36. };
  37. };
  38. if (traverse==false)
  39. return;
  40. if (n->M_left)
  41. dump_tree_node (n->M_left, is_set, traverse, dump_keys_and_values);
  42. if (n->M_right)
  43. dump_tree_node (n->M_right, is_set, traverse, dump_keys_and_values);
  44. };
  45. const char* ALOT_OF_TABS="\t\t\t\t\t\t\t\t\t\t\t";
  46. void dump_as_tree (int tabs, struct tree_node *n, bool is_set)
  47. {
  48. void *point_after_struct=((char*)n)+sizeof(struct tree_node);
  49. if (is_set)
  50. printf ("%d\n", *(int*)point_after_struct);
  51. else
  52. {
  53. struct map_pair *p=(struct map_pair *)point_after_struct;
  54. printf ("%d [%s]\n", p->key, p->value);
  55. }
  56. if (n->M_left)
  57. {
  58. printf ("%.*sL-------", tabs, ALOT_OF_TABS);
  59. dump_as_tree (tabs+1, n->M_left, is_set);
  60. };
  61. if (n->M_right)
  62. {
  63. printf ("%.*sR-------", tabs, ALOT_OF_TABS);
  64. dump_as_tree (tabs+1, n->M_right, is_set);
  65. };
  66. };
  67. void dump_map_and_set(struct tree_struct *m, bool is_set)
  68. {
  69. printf ("ptr=0x%p, M_key_compare=0x%x, M_header=0x%p, M_node_count=%d\n", m, m->M_key_compare, &m->M_header, m->M_node_count);
  70. dump_tree_node (m->M_header.M_parent, is_set, true, true);
  71. printf ("As a tree:\n");
  72. printf ("root----");
  73. dump_as_tree (1, m->M_header.M_parent, is_set);
  74. };
  75. int main()
  76. {
  77. // map
  78. std::map<int, const char*> m;
  79. m[10]="ten";
  80. m[20]="twenty";
  81. m[3]="three";
  82. m[101]="one hundred one";
  83. m[100]="one hundred";
  84. m[12]="twelve";
  85. m[107]="one hundred seven";
  86. m[0]="zero";
  87. m[1]="one";
  88. m[6]="six";
  89. m[99]="ninety-nine";
  90. m[5]="five";
  91. m[11]="eleven";
  92. m[1001]="one thousand one";
  93. m[1010]="one thousand ten";
  94. m[2]="two";
  95. m[9]="nine";
  96. printf ("dumping m as map:\n");
  97. dump_map_and_set ((struct tree_struct *)(void*)&m, false);
  98. std::map<int, const char*>::iterator it1=m.begin();
  99. printf ("m.begin():\n");
  100. dump_tree_node ((struct tree_node *)*(void**)&it1, false, false, true);
  101. it1=m.end();
  102. printf ("m.end():\n");
  103. dump_tree_node ((struct tree_node *)*(void**)&it1, false, false, false);
  104. // set
  105. std::set<int> s;
  106. s.insert(123);
  107. s.insert(456);
  108. s.insert(11);
  109. s.insert(12);
  110. s.insert(100);
  111. s.insert(1001);
  112. printf ("dumping s as set:\n");
  113. dump_map_and_set ((struct tree_struct *)(void*)&s, true);
  114. std::set<int>::iterator it2=s.begin();
  115. printf ("s.begin():\n");
  116. dump_tree_node ((struct tree_node *)*(void**)&it2, true, false, true);
  117. it2=s.end();
  118. printf ("s.end():\n");
  119. dump_tree_node ((struct tree_node *)*(void**)&it2, true, false, false);
  120. };

dumping m as map:

  1. ptr=0x0028FE3C, M_key_compare=0x402b70, M_header=0x0028FE40, M_node_count=17
  2. ptr=0x007A4988 M_left=0x007A4C00 M_parent=0x0028FE40 M_right=0x007A4B80 M_color=1
  3. key=10 value=[ten]
  4. ptr=0x007A4C00 M_left=0x007A4BE0 M_parent=0x007A4988 M_right=0x007A4C60 M_color=1
  5. key=1 value=[one]
  6. ptr=0x007A4BE0 M_left=0x00000000 M_parent=0x007A4C00 M_right=0x00000000 M_color=1
  7. key=0 value=[zero]
  8. ptr=0x007A4C60 M_left=0x007A4B40 M_parent=0x007A4C00 M_right=0x007A4C20 M_color=0
  9. key=5 value=[five]
  10. ptr=0x007A4B40 M_left=0x007A4CE0 M_parent=0x007A4C60 M_right=0x00000000 M_color=1
  11. key=3 value=[three]
  12. ptr=0x007A4CE0 M_left=0x00000000 M_parent=0x007A4B40 M_right=0x00000000 M_color=0
  13. key=2 value=[two]
  14. ptr=0x007A4C20 M_left=0x00000000 M_parent=0x007A4C60 M_right=0x007A4D00 M_color=1
  15. key=6 value=[six]
  16. ptr=0x007A4D00 M_left=0x00000000 M_parent=0x007A4C20 M_right=0x00000000 M_color=0
  17. key=9 value=[nine]
  18. ptr=0x007A4B80 M_left=0x007A49A8 M_parent=0x007A4988 M_right=0x007A4BC0 M_color=1
  19. key=100 value=[one hundred]
  20. ptr=0x007A49A8 M_left=0x007A4BA0 M_parent=0x007A4B80 M_right=0x007A4C40 M_color=0
  21. key=20 value=[twenty]
  22. ptr=0x007A4BA0 M_left=0x007A4C80 M_parent=0x007A49A8 M_right=0x00000000 M_color=1
  23. key=12 value=[twelve]
  24. ptr=0x007A4C80 M_left=0x00000000 M_parent=0x007A4BA0 M_right=0x00000000 M_color=0
  25. key=11 value=[eleven]
  26. ptr=0x007A4C40 M_left=0x00000000 M_parent=0x007A49A8 M_right=0x00000000 M_color=1
  27. key=99 value=[ninety-nine]
  28. ptr=0x007A4BC0 M_left=0x007A4B60 M_parent=0x007A4B80 M_right=0x007A4CA0 M_color=0
  29. key=107 value=[one hundred seven]
  30. ptr=0x007A4B60 M_left=0x00000000 M_parent=0x007A4BC0 M_right=0x00000000 M_color=1
  31. key=101 value=[one hundred one]
  32. ptr=0x007A4CA0 M_left=0x00000000 M_parent=0x007A4BC0 M_right=0x007A4CC0 M_color=1
  33. key=1001 value=[one thousand one]
  34. ptr=0x007A4CC0 M_left=0x00000000 M_parent=0x007A4CA0 M_right=0x00000000 M_color=0
  35. key=1010 value=[one thousand ten]
  36. As a tree:
  37. root----10 [ten]
  38. L-------1 [one]
  39. L-------0 [zero]
  40. R-------5 [five]
  41. L-------3 [three]
  42. L-------2 [two]
  43. R-------6 [six]
  44. R-------9 [nine]
  45. R-------100 [one hundred]
  46. L-------20 [twenty]
  47. L-------12 [twelve]
  48. L-------11 [eleven]
  49. R-------99 [ninety-nine]
  50. R-------107 [one hundred seven]
  51. L-------101 [one hundred one]
  52. R-------1001 [one thousand one]
  53. R-------1010 [one thousand ten]
  54. m.begin():
  55. ptr=0x007A4BE0 M_left=0x00000000 M_parent=0x007A4C00 M_right=0x00000000 M_color=1
  56. key=0 value=[zero]
  57. m.end():
  58. ptr=0x0028FE40 M_left=0x007A4BE0 M_parent=0x007A4988 M_right=0x007A4CC0 M_color=0
  59. dumping s as set:
  60. ptr=0x0028FE20, M_key_compare=0x8, M_header=0x0028FE24, M_node_count=6
  61. ptr=0x007A1E80 M_left=0x01D5D890 M_parent=0x0028FE24 M_right=0x01D5D850 M_color=1
  62. key=123
  63. ptr=0x01D5D890 M_left=0x01D5D870 M_parent=0x007A1E80 M_right=0x01D5D8B0 M_color=1
  64. key=12
  65. ptr=0x01D5D870 M_left=0x00000000 M_parent=0x01D5D890 M_right=0x00000000 M_color=0
  66. key=11
  67. ptr=0x01D5D8B0 M_left=0x00000000 M_parent=0x01D5D890 M_right=0x00000000 M_color=0
  68. key=100
  69. ptr=0x01D5D850 M_left=0x00000000 M_parent=0x007A1E80 M_right=0x01D5D8D0 M_color=1
  70. key=456
  71. ptr=0x01D5D8D0 M_left=0x00000000 M_parent=0x01D5D850 M_right=0x00000000 M_color=0
  72. key=1001
  73. As a tree:
  74. root----123
  75. L-------12
  76. L-------11
  77. R-------100
  78. R-------456
  79. R-------1001
  80. s.begin():
  81. ptr=0x01D5D870 M_left=0x00000000 M_parent=0x01D5D890 M_right=0x00000000 M_color=0
  82. key=11
  83. s.end():
  84. ptr=0x0028FE24 M_left=0x01D5D870 M_parent=0x007A1E80 M_right=0x01D5D8D0 M_color=0

GCC的实现也很类似。唯一一的不同点就是没有Isnil元素,因此占用的空间要小于MSVC的实现。跟节点也是作为.end()迭代器指向的位置,同时也不包含键和值。

重新平衡的演示

下面的例子将向我们展示插入节点后,树如何重新平衡。

  1. #include <stdio.h>
  2. #include <map>
  3. #include <set>
  4. #include <string>
  5. #include <iostream>
  6. struct map_pair
  7. {
  8. int key;
  9. const char *value;
  10. };
  11. struct tree_node
  12. {
  13. int M_color; // 0 - Red, 1 - Black
  14. struct tree_node *M_parent;
  15. struct tree_node *M_left;
  16. struct tree_node *M_right;
  17. };
  18. struct tree_struct
  19. {
  20. int M_key_compare;
  21. struct tree_node M_header;
  22. size_t M_node_count;
  23. };
  24. const char* ALOT_OF_TABS="\t\t\t\t\t\t\t\t\t\t\t";
  25. void dump_as_tree (int tabs, struct tree_node *n)
  26. {
  27. void *point_after_struct=((char*)n)+sizeof(struct tree_node);
  28. printf ("%d\n", *(int*)point_after_struct);
  29. if (n->M_left)
  30. {
  31. printf ("%.*sL-------", tabs, ALOT_OF_TABS);
  32. dump_as_tree (tabs+1, n->M_left);
  33. };
  34. if (n->M_right)
  35. {
  36. printf ("%.*sR-------", tabs, ALOT_OF_TABS);
  37. dump_as_tree (tabs+1, n->M_right);
  38. };
  39. };
  40. void dump_map_and_set(struct tree_struct *m)
  41. {
  42. printf ("root----");
  43. dump_as_tree (1, m->M_header.M_parent);
  44. };
  45. int main()
  46. {
  47. std::set<int> s;
  48. s.insert(123);
  49. s.insert(456);
  50. printf ("123, 456 are inserted\n");
  51. dump_map_and_set ((struct tree_struct *)(void*)&s);
  52. s.insert(11);
  53. s.insert(12);
  54. printf ("\n");
  55. printf ("11, 12 are inserted\n");
  56. dump_map_and_set ((struct tree_struct *)(void*)&s);
  57. s.insert(100);
  58. s.insert(1001);
  59. printf ("\n");
  60. printf ("100, 1001 are inserted\n");
  61. dump_map_and_set ((struct tree_struct *)(void*)&s);
  62. s.insert(667);
  63. s.insert(1);
  64. s.insert(4);
  65. s.insert(7);
  66. printf ("\n");
  67. printf ("667, 1, 4, 7 are inserted\n");
  68. dump_map_and_set ((struct tree_struct *)(void*)&s);
  69. printf ("\n");
  70. };
  1. 123, 456 are inserted
  2. root----123
  3. R-------456
  4. 11, 12 are inserted
  5. root----123
  6. L-------11
  7. R-------12
  8. R-------456
  9. 100, 1001 are inserted
  10. root----123
  11. L-------12
  12. L-------11
  13. R-------100
  14. R-------456
  15. R-------1001
  16. 667, 1, 4, 7 are inserted
  17. root----12
  18. L-------4
  19. L-------1
  20. R-------11
  21. L-------7
  22. R-------123
  23. L-------100
  24. R-------667
  25. L-------456
  26. R-------1001

54.10位。

所有位操作工作,与其他的一些ISA(指令集架构)类似:

  1. public static int set (int a, int b)
  2. {
  3. return a | 1<<b;
  4. }
  5. public static int clear (int a, int b)
  6. {
  7. return a & (~(1<<b));
  8. }
  9. public static int set(int, int);
  10. flags: ACC_PUBLIC, ACC_STATIC
  11. Code:
  12. stack=3, locals=2, args_size=2
  13. 0: iload_0
  14. 1: iconst_1
  15. 2: iload_1
  16. 3: ishl
  17. 4: ior
  18. 5: ireturn
  19. public static int clear(int, int);
  20. flags: ACC_PUBLIC, ACC_STATIC
  21. Code:
  22. stack=3, locals=2, args_size=2
  23. 0: iload_0
  24. 1: iconst_1
  25. 2: iload_1
  26. 3: ishl
  27. 4: iconst_m1
  28. 5: ixor
  29. 6: iand
  30. 7: ireturn

926 iconst_m1将-1入栈,这数其实就是16进制的0xFFFFFFFF,将0xFFFFFFFF作为XOR-ing指令执行的操作数。起到的效果就是把所有bits位反向,(A.6.2在1406页)

我将所有数据类型,扩展成64为长整型。

  1. public static long lset (long a, int b)
  2. {
  3. return a | 1<<b;
  4. }
  5. public static long lclear (long a, int b)
  6. {
  7. return a & (~(1<<b));
  8. }
  9. public static long lset(long, int);
  10. flags: ACC_PUBLIC, ACC_STATIC
  11. Code:
  12. stack=4, locals=3, args_size=2
  13. 0: lload_0
  14. 1: iconst_1
  15. 2: iload_2
  16. 3: ishl
  17. 4: i2l
  18. 5: lor
  19. 6: lreturn
  20. public static long lclear(long, int);
  21. flags: ACC_PUBLIC, ACC_STATIC
  22. Code:
  23. stack=4, locals=3, args_size=2
  24. 0: lload_0
  25. 1: iconst_1
  26. 2: iload_2
  27. 3: ishl
  28. 4: iconst_m1
  29. 5: ixor
  30. 6: i2l
  31. 7: land
  32. 8: lreturn

代码是相同的,但是指令前面使用了前缀L,操作64位值,并且第二个函数参数还是int类型,并且32值需要升级为64位值,值被i21指令使用,本质上 就是把整型,扩展成64位长整型.

927页

54.12 switch()函数

switch()语句的实现是用tableswitch指令,

  1. public static void f(int a)
  2. {
  3. switch (a)
  4. {
  5. case 0: System.out.println("zero"); break;
  6. case 1: System.out.println("one\n"); break;
  7. case 2: System.out.println("two\n"); break;
  8. case 3: System.out.println("three\n"); break;
  9. case 4: System.out.println("four\n"); break;
  10. default: System.out.println("something unknown\n"); break;
  11. };
  12. }

尽可能简单的例子

  1. public static void f(int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=2, locals=1, args_size=1
  5. 0: iload_0
  6. 1: tableswitch { // 0 to 4
  7. 0: 36
  8. 1: 47
  9. 2: 58
  10. 3: 69
  11. 4: 80
  12. default: 91
  13. }
  14. 36: getstatic #2 // Field java/⤦
  15. Ç lang/System.out:Ljava/io/PrintStream;
  16. 39: ldc #3 // String zero
  17. 41: invokevirtual #4 // Method java/io⤦
  18. Ç /PrintStream.println:(Ljava/lang/String;)V
  19. 44: goto 99
  20. 47: getstatic #2 // Field java/⤦
  21. Ç lang/System.out:Ljava/io/PrintStream;
  22. 50: ldc #5 // String one\n
  23. 52: invokevirtual #4 // Method java/io⤦
  24. Ç /PrintStream.println:(Ljava/lang/String;)V
  25. 55: goto 99
  26. 58: getstatic #2 // Field java/⤦
  27. Ç lang/System.out:Ljava/io/PrintStream;
  28. 61: ldc #6 // String two\n
  29. 63: invokevirtual #4 // Method java/io⤦
  30. Ç /PrintStream.println:(Ljava/lang/String;)V
  31. 66: goto 99
  32. 69: getstatic #2 // Field java/⤦
  33. Ç lang/System.out:Ljava/io/PrintStream;
  34. 72: ldc #7 // String three\n
  35. 74: invokevirtual #4 // Method java/io⤦
  36. Ç /PrintStream.println:(Ljava/lang/String;)V
  37. 77: goto 99
  38. 80: getstatic #2 // Field java/⤦
  39. Ç lang/System.out:Ljava/io/PrintStream;
  40. 83: ldc #8 // String four\n
  41. 85: invokevirtual #4 // Method java/io⤦
  42. Ç /PrintStream.println:(Ljava/lang/String;)V
  43. 88: goto 99
  44. 91: getstatic #2 // Field java/⤦
  45. Ç lang/System.out:Ljava/io/PrintStream;
  46. 94: ldc #9 // String ⤦
  47. Ç something unknown\n
  48. 931
  49. CHAPTER 54. JAVA 54.13. ARRAYS
  50. 96: invokevirtual #4 // Method java/io⤦
  51. Ç /PrintStream.println:(Ljava/lang/String;)V
  52. 99: return

930

931

54.14 字符串 54.14.1 第一个例子

字符串也是对象,和其他对象的构造方式相同。(还有数组)

  1. public static void main(String[] args)
  2. {
  3. System.out.println("What is your name?");
  4. String input = System.console().readLine();
  5. System.out.println("Hello, "+input);
  6. }
  7. public static void main(java.lang.String[]);
  8. flags: ACC_PUBLIC, ACC_STATIC
  9. Code:
  10. stack=3, locals=2, args_size=1
  11. 0: getstatic #2 // Field java/⤦
  12. Ç lang/System.out:Ljava/io/PrintStream;
  13. 3: ldc #3 // String What is⤦
  14. Ç your name?
  15. 5: invokevirtual #4 // Method java/io⤦
  16. Ç /PrintStream.println:(Ljava/lang/String;)V
  17. 8: invokestatic #5 // Method java/⤦
  18. Ç lang/System.console:()Ljava/io/Console;
  19. 11: invokevirtual #6 // Method java/io⤦
  20. Ç /Console.readLine:()Ljava/lang/String;
  21. 14: astore_1
  22. 15: getstatic #2 // Field java/⤦
  23. Ç lang/System.out:Ljava/io/PrintStream;
  24. 18: new #7 // class java/⤦
  25. Ç lang/StringBuilder
  26. 21: dup
  27. 22: invokespecial #8 // Method java/⤦
  28. Ç lang/StringBuilder."<init>":()V
  29. 25: ldc #9 // String Hello,
  30. 27: invokevirtual #10 // Method java/⤦
  31. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  32. Ç StringBuilder;
  33. 30: aload_1
  34. 31: invokevirtual #10 // Method java/⤦
  35. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  36. Ç StringBuilder;
  37. 34: invokevirtual #11 // Method java/⤦
  38. Ç lang/StringBuilder.toString:()Ljava/lang/String;
  39. 37: invokevirtual #4 // Method java/io⤦
  40. Ç /PrintStream.println:(Ljava/lang/String;)V
  41. 40: return

944 在11行偏移调用了readline()方法,字符串引用(由用户提供)被存储在栈顶,在14行偏移,字符串引用被存储在LVA的1号槽中。

用户输入的字符串在30行偏移处重新加载并和 “hello”字符进行了链接,使用的是StringBulder类,在17行偏移,构造的字符串被pirntln方法打印。

54.14.2 第二个例子 另外一个例子

  1. public class strings
  2. {
  3. public static char test (String a)
  4. {
  5. return a.charAt(3);
  6. };
  7. public static String concat (String a, String b)
  8. {
  9. return a+b;
  10. }
  11. }
  12. public static char test(java.lang.String);
  13. flags: ACC_PUBLIC, ACC_STATIC
  14. Code:
  15. stack=2, locals=1, args_size=1
  16. 0: aload_0
  17. 1: iconst_3
  18. 2: invokevirtual #2 // Method java/⤦
  19. Ç lang/String.charAt:(I)C
  20. 5: ireturn

945

字符串的链接使用用StringBuilder类完成。

  1. public static java.lang.String concat(java.lang.String, java.⤦
  2. Ç lang.String);
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=2, locals=2, args_size=2
  6. 0: new #3 // class java/⤦
  7. Ç lang/StringBuilder
  8. 3: dup
  9. 4: invokespecial #4 // Method java/⤦
  10. Ç lang/StringBuilder."<init>":()V
  11. 7: aload_0
  12. 8: invokevirtual #5 // Method java/⤦
  13. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  14. Ç StringBuilder;
  15. 11: aload_1
  16. 12: invokevirtual #5 // Method java/⤦
  17. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  18. Ç StringBuilder;
  19. 15: invokevirtual #6 // Method java/⤦
  20. Ç lang/StringBuilder.toString:()Ljava/lang/String;
  21. 18: areturn

另外一个例子

  1. public static void main(String[] args)
  2. {
  3. String s="Hello!";
  4. int n=123;
  5. System.out.println("s=" + s + " n=" + n);
  6. }

字符串构造用StringBuilder类,和它的添加方法,被构造的字符串被传递给println方法。

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=3, locals=3, args_size=1
  5. 0: ldc #2 // String Hello!
  6. 2: astore_1
  7. 3: bipush 123
  8. 5: istore_2
  9. 6: getstatic #3 // Field java/⤦
  10. Ç lang/System.out:Ljava/io/PrintStream;
  11. 9: new #4 // class java/⤦
  12. Ç lang/StringBuilder
  13. 12: dup
  14. 13: invokespecial #5 // Method java/⤦
  15. Ç lang/StringBuilder."<init>":()V
  16. 16: ldc #6 // String s=
  17. 18: invokevirtual #7 // Method java/⤦
  18. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  19. Ç StringBuilder;
  20. 21: aload_1
  21. 22: invokevirtual #7 // Method java/⤦
  22. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  23. Ç StringBuilder;
  24. 25: ldc #8 // String n=
  25. 27: invokevirtual #7 // Method java/⤦
  26. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  27. Ç StringBuilder;
  28. 30: iload_2
  29. 31: invokevirtual #9 // Method java/⤦
  30. Ç lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  31. 34: invokevirtual #10 // Method java/⤦
  32. Ç lang/StringBuilder.toString:()Ljava/lang/String;
  33. 37: invokevirtual #11 // Method java/io⤦
  34. Ç /PrintStream.println:(Ljava/lang/String;)V
  35. 40: return

946

54.16 类 简单类

清单 54.15: test.java

  1. public class test
  2. {
  3. public static int a;
  4. private static int b;
  5. public test()
  6. {
  7. a=0;
  8. b=0;
  9. }
  10. public static void set_a (int input)
  11. {
  12. a=input;
  13. }
  14. public static int get_a ()
  15. {
  16. return a;
  17. }
  18. public static void set_b (int input)
  19. {
  20. b=input;
  21. }
  22. public static int get_b ()
  23. {
  24. return b;
  25. }
  26. }

952

构造函数,只是把两个之段设置成0.

  1. public test();
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/⤦
  7. Ç lang/Object."<init>":()V
  8. 4: iconst_0
  9. 5: putstatic #2 // Field a:I
  10. 8: iconst_0
  11. 9: putstatic #3 // Field b:I
  12. 12: return

a的设定器

  1. public static void set_a(int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: iload_0
  6. 1: putstatic #2 // Field a:I
  7. 4: return

a的取得器

  1. public static int get_a();
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=0, args_size=0
  5. 0: getstatic #2 // Field a:I
  6. 3: ireturn

b的设定器

  1. public static void set_b(int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: iload_0
  6. 1: putstatic #3 // Field b:I
  7. 4: return

b的取得器

  1. public static int get_b();
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=0, args_size=0
  5. 0: getstatic #3 // Field b:I
  6. 3: ireturn

953 类中的公有和私有字段代码没什么区别。 但是类型信息会在in.class 文件中表示,并且,无论如何私有变量是不可以被访问的。

让我们创建对象并调用方法: 清单 54.16: ex1.java

954 新指令创建对象,但不调用构造函数(它在4行偏移被调用)set_a()方法被在16行偏移被调用,字段访问使用的getstatic指令,在行偏移21。

  1. Listing 54.16: ex1.java
  2. public class ex1
  3. {
  4. public static void main(String[] args)
  5. {
  6. test obj=new test();
  7. obj.set_a (1234);
  8. System.out.println(obj.a);
  9. }
  10. }
  11. public static void main(java.lang.String[]);
  12. flags: ACC_PUBLIC, ACC_STATIC
  13. Code:
  14. stack=2, locals=2, args_size=1
  15. 0: new #2 // class test
  16. 3: dup
  17. 4: invokespecial #3 // Method test."<⤦
  18. Ç init>":()V
  19. 7: astore_1
  20. 8: aload_1
  21. 9: pop
  22. 10: sipush 1234
  23. 13: invokestatic #4 // Method test.⤦
  24. Ç set_a:(I)V
  25. 16: getstatic #5 // Field java/⤦
  26. Ç lang/System.out:Ljava/io/PrintStream;
  27. 19: aload_1
  28. 20: pop
  29. 21: getstatic #6 // Field test.a:I
  30. 24: invokevirtual #7 // Method java/io⤦
  31. Ç /PrintStream.println:(I)V
  32. 27: return

5.4 章

54.1介绍 大家都知道,java有很多的反编译器(或是产生JVM字节码) 原因是JVM字节码比其他的X86低级代码更容易进行反编译。

a).多很多相关数据类型的信息。 b).JVM(java虚拟机)内存模型更严格和概括。 c).java编译器没有做任何的优化工作(JVM JIT不是实时),所以,类文件中的字节代码的通常更清晰易读。

JVM字节码知识什么时候有用呢?

a).文件的快速粗糙的打补丁任务,类文件不需要重新编译反编译的结果。 b).分析混淆代码 c).创建你自己的混淆器。 d).创建编译器代码生成器(后端)目标。

我们从一段简短的代码开始,除非特殊声明,我们用的都是JDK1.7

反编译类文件使用的命令,随处可见:javap -c -verbase.

在这本书中提供的很多的例子,都用到了这个。

54.3 简单的计算函数

让我们继续看简单的计算函数。

  1. public class calc
  2. {
  3. public static int half(int a)
  4. {
  5. return a/2;
  6. }
  7. }

这种情况使用icont_2会被使用。

  1. public static int half(int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=2, locals=1, args_size=1
  5. 0: iload_0
  6. 1: iconst_2
  7. 2: idiv
  8. 3: ireturn

iload_0 将零给函数做参数,然后将其入栈。iconst_2将2入栈,这两个指令执行后,栈看上去是这个样子的。

  1. +---+
  2. TOS ->| 2 |
  3. +---+
  4. | a |
  5. +---+

idiv携带两个值在栈顶, divides 只有一个值,返回结果在栈顶。

  1. +--------+
  2. TOS ->| result |
  3. +--------+

ireturn取得比返回。 让我们处理双精度浮点整数。

  1. public class calc
  2. {
  3. public static double half_double(double a)
  4. {
  5. return a/2.0;
  6. }
  7. }

清单54.7 常量区

  1. ...
  2. #2 = Double 2.0d
  3. ...
  4. public static double half_double(double);
  5. flags: ACC_PUBLIC, ACC_STATIC
  6. Code:
  7. stack=4, locals=2, args_size=1
  8. 0: dload_0
  9. 1: ldc2_w #2 // double 2.0d
  10. 4: ddiv
  11. 5: dreturn

类似,只是ldc2_w指令是从常量区装载2.0,另外,所有其他三条指令有d前缀,意思是他们工作在double数据类型下。

我们现在使用两个参数的函数。

  1. public class calc
  2. {
  3. public static int sum(int a, int b)
  4. {
  5. return a+b;
  6. }
  7. }
  8. public static int sum(int, int);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=2, locals=2, args_size=2
  12. 0: iload_0
  13. 1: iload_1
  14. 2: iadd
  15. 3: ireturn

iload_0加载第一个函数参数(a),iload_2 第二个参数(b)下面两条指令执行后,栈的情况如下:

  1. +---+
  2. TOS ->| b |
  3. +---+
  4. | a |
  5. +---+

iadds 增加两个值,返回结果在栈顶。 +——–+ TOS ->| result | +——–+

让我们把这个例子扩展成长整型数据类型。

  1. public static long lsum(long a, long b)
  2. {
  3. return a+b;
  4. }

我们得到的是:

  1. public static long lsum(long, long);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=4, locals=4, args_size=2
  5. 0: lload_0
  6. 1: lload_2
  7. 2: ladd
  8. 3: lreturn

第二个(load指令从第二参数槽中,取得第二参数。这是因为64位长整型的值占用来位,用了另外的话2位参数槽。)

稍微复杂的例子

  1. public class calc
  2. {
  3. public static int mult_add(int a, int b, int c)
  4. {
  5. return a*b+c;
  6. }
  7. }
  8. public static int mult_add(int, int, int);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=2, locals=3, args_size=3
  12. 0: iload_0
  13. 1: iload_1
  14. 2: imul
  15. 3: iload_2
  16. 4: iadd
  17. 5: ireturn

第一是相乘,积被存储在栈顶。

  1. +---------+
  2. TOS ->| product |
  3. +---------+

iload_2加载第三个参数(C)入栈。

  1. +---------+
  2. TOS ->| c |
  3. +---------+
  4. | product |
  5. +---------+

现在iadd指令可以相加两个值。

915

54.5 简单的函数调用 mathrandom()返回一个伪随机数,函数范围在「0.0…1.0)之间,但对我们来说,由于一些原因,我们常常需要设计一个函数返回数值范围在「0.0…0.5)

  1. public class HalfRandom
  2. {
  3. public static double f()
  4. {
  5. return Math.random()/2;
  6. }
  7. }

54.8 常量区

  1. ...
  2. #2 = Methodref #18.#19 // java/lang/Math.⤦
  3. Ç random:()D
  4. 6(Java) Local Variable Array
  5. #3 = Double 2.0d
  6. ...
  7. #12 = Utf8 ()D
  8. ...
  9. #18 = Class #22 // java/lang/Math
  10. #19 = NameAndType #23:#12 // random:()D
  11. #22 = Utf8 java/lang/Math
  12. #23 = Utf8 random
  13. public static double f();
  14. flags: ACC_PUBLIC, ACC_STATIC
  15. Code:
  16. stack=4, locals=0, args_size=0
  17. 0: invokestatic #2 // Method java/⤦
  18. Ç lang/Math.random:()D
  19. 3: ldc2_w #3 // double 2.0d
  20. 6: ddiv
  21. 7: dreturn

java本地变量数组 916 静态执行调用math.random()函数,返回值在栈顶。结果是被0.5初返回的,但函数名是怎么被编码的呢? 在常量区使用methodres表达式,进行编码的,它定义类和方法的名称。第一个methodref 字段指向表达式,其次,指向通常文本字符(“java/lang/math”) 第二个methodref表达指向名字和类型表达式,同时链接两个字符。第一个方法的名字式字符串“random”,第二个字符串是“()D”,来编码函数类型,它以为这两个值(因此D是字符串)这种方式1JVM可以检查数据类型的正确性:2)java反编译器可以从被编译的类文件中修改数据类型。

最后,我们试着使用“hello,world!”作为例子。

  1. public class HelloWorld
  2. {
  3. public static void main(String[] args)
  4. {
  5. System.out.println("Hello, World");
  6. }
  7. }

54.9 常量区

917 常量区的ldc行偏移3,指向“hello,world!”字符串,并且将其入栈,在java里它被成为饮用,其实它就是指针,或是地址。

918

  1. ...
  2. #2 = Fieldref #16.#17 // java/lang/System.⤦
  3. Ç out:Ljava/io/PrintStream;
  4. #3 = String #18 // Hello, World
  5. #4 = Methodref #19.#20 // java/io/⤦
  6. Ç PrintStream.println:(Ljava/lang/String;)V
  7. ...
  8. #16 = Class #23 // java/lang/System
  9. #17 = NameAndType #24:#25 // out:Ljava/io/⤦
  10. Ç PrintStream;
  11. #18 = Utf8 Hello, World
  12. #19 = Class #26 // java/io/⤦
  13. Ç PrintStream
  14. #20 = NameAndType #27:#28 // println:(Ljava/⤦
  15. Ç lang/String;)V
  16. ...
  17. #23 = Utf8 java/lang/System
  18. #24 = Utf8 out
  19. #25 = Utf8 Ljava/io/PrintStream;
  20. #26 = Utf8 java/io/PrintStream
  21. #27 = Utf8 println
  22. #28 = Utf8 (Ljava/lang/String;)V
  23. ...
  24. public static void main(java.lang.String[]);
  25. flags: ACC_PUBLIC, ACC_STATIC
  26. Code:
  27. stack=2, locals=1, args_size=1
  28. 0: getstatic #2 // Field java/⤦
  29. Ç lang/System.out:Ljava/io/PrintStream;
  30. 3: ldc #3 // String Hello, ⤦
  31. Ç World
  32. 5: invokevirtual #4 // Method java/io⤦
  33. Ç /PrintStream.println:(Ljava/lang/String;)V
  34. 8: return

常见的invokevirtual指令,从常量区取信息,然后调用pringln()方法,貌似我们知道的println()方法,适用于各种数据类型,我这种println()函数版本,预先给的是字符串类型。

但是第一个getstatic指令是干什么的?这条指令取得对象信息的字段的一个引用或是地址。输出并将其进栈,这个值实际更像是println放的指针,因此,内部的print method取得两个参数,输入1指向对象的this指针,2)“hello,world”字符串的地址,确实,println()在被初始化系统的调用,对象之外,为了方便,javap使用工具把所有的信息都写入到注释中。

54.7 线性同余伪随机数生成器 我们来试一个简单的伪随机函数生成器,我已经在这本书中用过一次了。(在500页20行) 919

  1. public class LCG
  2. {
  3. public static int rand_state;
  4. public void my_srand (int init)
  5. {
  6. rand_state=init;
  7. }
  8. public static int RNG_a=1664525;
  9. public static int RNG_c=1013904223;
  10. public int my_rand ()
  11. {
  12. rand_state=rand_state*RNG_a;
  13. rand_state=rand_state+RNG_c;
  14. return rand_state & 0x7fff;
  15. }
  16. }

一对类的字段,在最开始时被初始化。但是怎么能,在javap的输出中,发现类的构造呢?

  1. static {};
  2. flags: ACC_STATIC
  3. Code:
  4. stack=1, locals=0, args_size=0
  5. 0: ldc #5 // int 1664525
  6. 2: putstatic #3 // Field RNG_a:I
  7. 5: ldc #6 // int 1013904223
  8. 7: putstatic #4 // Field RNG_c:I
  9. 10: return

这种变量的初始化,RNG_a占用了3个参数槽,iRNG_C是4个,而puststatic指令是,用于设定常量。

my_srand()函数,只是将输入值,存储到rand_state中;

  1. public void my_srand(int);
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=1, locals=2, args_size=2
  5. 0: iload_1
  6. 1: putstatic #2 // Field ⤦
  7. Ç rand_state:I
  8. 4: return

iload_1 取得输入值并将其入栈。但为什么不用iload_0?因为这个函数可能使用类的字段属性,因此这个变量被作为参数0传递给了函数,rand_state字段属性,在类中占用2个参数槽子。

现在my_rand():

  1. public int my_rand();
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=2, locals=1, args_size=1
  5. 0: getstatic #2 // Field ⤦
  6. Ç rand_state:I
  7. 3: getstatic #3 // Field RNG_a:I
  8. 6: imul
  9. 7: putstatic #2 // Field ⤦
  10. Ç rand_state:I
  11. 10: getstatic #2 // Field ⤦
  12. Ç rand_state:I
  13. 13: getstatic #4 // Field RNG_c:I
  14. 16: iadd
  15. 17: putstatic #2 // Field ⤦
  16. Ç rand_state:I
  17. 20: getstatic #2 // Field ⤦
  18. Ç rand_state:I
  19. 23: sipush 32767
  20. 26: iand
  21. 27: ireturn

它仅是加载了所有对象字段的值。在20行偏移,操作和更新rand_state,使用putstatic指令。

rand_state 值被再次重载(因为之前,使用过putstatic指令,其被从栈中弃出)这种代码其实比较低效率,但是可以肯定的是,JVM会经常的,对其进行很好的优化。

54.9 传递参数值

我们来扩展一下min()/max()这个例子。

  1. public class minmax
  2. {
  3. public static int min (int a, int b)
  4. {
  5. if (a>b)
  6. return b;
  7. return a;
  8. }
  9. public static int max (int a, int b)
  10. {
  11. if (a>b)
  12. return a;
  13. return b;
  14. }
  15. public static void main(String[] args)
  16. {
  17. int a=123, b=456;
  18. int max_value=max(a, b);
  19. int min_value=min(a, b);
  20. System.out.println(min_value);
  21. System.out.println(max_value);
  22. }
  23. }

924 这是main()函数的代码。

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=2, locals=5, args_size=1
  5. 0: bipush 123
  6. 2: istore_1
  7. 3: sipush 456
  8. 6: istore_2
  9. 7: iload_1
  10. 8: iload_2
  11. 9: invokestatic #2 // Method max:(II⤦
  12. Ç )I
  13. 12: istore_3
  14. 13: iload_1
  15. 14: iload_2
  16. 15: invokestatic #3 // Method min:(II⤦
  17. Ç )I
  18. 18: istore 4
  19. 20: getstatic #4 // Field java/⤦
  20. Ç lang/System.out:Ljava/io/PrintStream;
  21. 23: iload 4
  22. 25: invokevirtual #5 // Method java/io⤦
  23. Ç /PrintStream.println:(I)V
  24. 28: getstatic #4 // Field java/⤦
  25. Ç lang/System.out:Ljava/io/PrintStream;
  26. 31: iload_3
  27. 32: invokevirtual #5 // Method java/io⤦
  28. Ç /PrintStream.println:(I)V
  29. 35: return

925 参数在栈中的被传给其他函数,返回值在栈顶。

54.11循环

  1. public class Loop
  2. {
  3. public static void main(String[] args)
  4. {
  5. for (int i = 1; i <= 10; i++)
  6. {
  7. System.out.println(i);
  8. }
  9. }
  10. }
  11. public static void main(java.lang.String[]);
  12. flags: ACC_PUBLIC, ACC_STATIC
  13. Code:
  14. stack=2, locals=2, args_size=1
  15. 0: iconst_1
  16. 1: istore_1
  17. 2: iload_1
  18. 3: bipush 10
  19. 5: if_icmpgt 21
  20. 8: getstatic #2 // Field java/⤦
  21. Ç lang/System.out:Ljava/io/PrintStream;
  22. 11: iload_1
  23. 12: invokevirtual #3 // Method java/io⤦
  24. Ç /PrintStream.println:(I)V
  25. 15: iinc 1, 1
  26. 18: goto 2
  27. 21: return

icont_1将1推入到栈顶,istore_1将其存入到LVA的参数槽1,为什么没有零槽?因为main()函数只有一个参数,并且指向其的引用,就在第0号槽中。

因此,i本地变量总是在1号参数槽中。 指令在行3偏移和行5偏移,将i和10的比较。如果i大,执行流进入行21偏移,函数结束了,如果不被println调用。i在行11偏移进行了重新加载,之后给println使用。

多说一句,我们调用pringln打印数据类型是整型,我们看注释,“i,v”,i的意思是整型,v的意思是返回void。

当println函数结束,i是步进到行15偏移,指令第一个操作数是参数槽1的值。第二个是数值1与本地变量相加结果。

goto指令就是跳转,它跳转到循环体的开始地址,再行偏移2.

928页

让我们进行更复杂的例子。

  1. public class Fibonacci
  2. {
  3. public static void main(String[] args)
  4. {
  5. int limit = 20, f = 0, g = 1;
  6. for (int i = 1; i <= limit; i++)
  7. {
  8. f = f + g;
  9. g = f - g;
  10. System.out.println(f);
  11. }
  12. }
  13. }
  14. public static void main(java.lang.String[]);
  15. flags: ACC_PUBLIC, ACC_STATIC
  16. Code:
  17. stack=2, locals=5, args_size=1
  18. 0: bipush 20
  19. 2: istore_1
  20. 3: iconst_0
  21. 4: istore_2
  22. 5: iconst_1
  23. 6: istore_3
  24. 7: iconst_1
  25. 8: istore 4
  26. 10: iload 4
  27. 12: iload_1
  28. 13: if_icmpgt 37
  29. 16: iload_2
  30. 17: iload_3
  31. 18: iadd
  32. 19: istore_2
  33. 20: iload_2
  34. 21: iload_3
  35. 22: isub
  36. 23: istore_3
  37. 24: getstatic #2 // Field java/⤦
  38. Ç lang/System.out:Ljava/io/PrintStream;
  39. 27: iload_2
  40. 28: invokevirtual #3 // Method java/io⤦
  41. Ç /PrintStream.println:(I)V
  42. 31: iinc 4, 1
  43. 34: goto 10
  44. 37: return

929 LVA槽中参数映射。 0-main()的唯一参数。 1-限制,总是20. 2-f 3-g 4-i

我们可以看到java编译器在LVA参数槽分配变量,并且是相同的顺序,就像在源代码中声明变量。

分离指令istore,是用于访问参数槽0123,但是不能大于4,因此,附加一些操作,在行2,8偏移,使用槽中数据作为操作数,类似于在偏移10位置的iload指令。

无可口非,分离其他的槽,限制变量总是20(其本质上就是一个常数),重加载值很经常吗?

JVM JIT 编译器经常可以对其优化的很好。在代码中人工的干预优化其实是没有什么太大价值的。

54.13数组

54.13.1简单的例子 我们首先创建一个长度是10的整型的数组,对其初始化。

  1. public static void main(String[] args)
  2. {
  3. int a[]=new int[10];
  4. for (int i=0; i<10; i++)
  5. a[i]=i;
  6. dump (a);
  7. }
  8. public static void main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=3, locals=3, args_size=1
  12. 0: bipush 10
  13. 2: newarray int
  14. 4: astore_1
  15. 5: iconst_0
  16. 6: istore_2
  17. 7: iload_2
  18. 8: bipush 10
  19. 10: if_icmpge 23
  20. 13: aload_1
  21. 14: iload_2
  22. 15: iload_2
  23. 16: iastore
  24. 17: iinc 2, 1
  25. 20: goto 7
  26. 23: aload_1
  27. 24: invokestatic #4 // Method dump:([⤦
  28. Ç I)V
  29. 27: return

newarray指令,创建了一个有10个整数元素的数组,数组的大小设置使用bipush指令,然后结果会返回到栈顶。数组类型用newarry指令操作符,进行设定。

932

newarray被执行后,引用(指针)到新创建的数据,栈顶的槽中,astore_1存储引用指向到LVA的一号槽,main()函数的第二个部分,是循环的存储值1到相应的素组元素。 aload_1得到数据的引用并放入到栈中。lastore将integer值从堆中存储到素组中,引用当前的栈顶。main()函数代用dump()的函数部分,参数是,准备给aload_1指令的(行偏移23)

现在我们进入dump()函数。

  1. public static void dump(int a[])
  2. {
  3. for (int i=0; i<a.length; i++)
  4. System.out.println(a[i]);
  5. }
  6. public static void dump(int[]);
  7. flags: ACC_PUBLIC, ACC_STATIC
  8. Code:
  9. stack=3, locals=2, args_size=1
  10. 0: iconst_0
  11. 1: istore_1
  12. 2: iload_1
  13. 3: aload_0
  14. 4: arraylength
  15. 5: if_icmpge 23
  16. 8: getstatic #2 // Field java/⤦
  17. Ç lang/System.out:Ljava/io/PrintStream;
  18. 11: aload_0
  19. 12: iload_1
  20. 13: iaload
  21. 14: invokevirtual #3 // Method java/io⤦
  22. Ç /PrintStream.println:(I)V
  23. 17: iinc 1, 1
  24. 20: goto 2
  25. 23: return

到了引用的数组在0槽,a.length表达式在源代码中是转化到arraylength指令,它取得数组的引用,并且数组的大小在栈顶。 iaload在行偏移13被用于装载数据元素。 它需要在堆栈中的数组引用。用aload_0 11并且索引(用iload_1在行偏移12准备)

无可厚非,指令前缀可能会被错误的理解,就像数组指令,那样不正确,这些指令和对象的引用一起工作的。数组和字符串都是对象。

54.13.2 数组元素的求和

另外的例子

  1. public class ArraySum
  2. {
  3. public static int f (int[] a)
  4. {
  5. int sum=0;
  6. for (int i=0; i<a.length; i++)
  7. sum=sum+a[i];
  8. return sum;
  9. }
  10. }
  11. public static int f(int[]);
  12. flags: ACC_PUBLIC, ACC_STATIC
  13. Code:
  14. stack=3, locals=3, args_size=1
  15. 0: iconst_0
  16. 1: istore_1
  17. 2: iconst_0
  18. 3: istore_2
  19. 4: iload_2
  20. 5: aload_0
  21. 6: arraylength
  22. 7: if_icmpge 22
  23. 10: iload_1
  24. 11: aload_0
  25. 12: iload_2
  26. 13: iaload
  27. 14: iadd
  28. 15: istore_1
  29. 16: iinc 2, 1
  30. 19: goto 4
  31. 22: iload_1
  32. 23: ireturn

LVA槽0是数组的引用,LVA槽1是本地变量和。

54.13.3 main()函数唯一的数据参数

让我们使用唯一的main()函数参数,字符串数组。

  1. public class UseArgument
  2. {
  3. public static void main(String[] args)
  4. {
  5. System.out.print("Hi, ");
  6. System.out.print(args[1]);
  7. System.out.println(". How are you?");
  8. }
  9. }

934 0参(argument)第0个参数是程序(和C/C++类似)

因此第一个参数,而第一参数是拥护提供的。

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=3, locals=1, args_size=1
  5. 0: getstatic #2 // Field java/⤦
  6. Ç lang/System.out:Ljava/io/PrintStream;
  7. 3: ldc #3 // String Hi,
  8. 5: invokevirtual #4 // Method java/io⤦
  9. Ç /PrintStream.print:(Ljava/lang/String;)V
  10. 8: getstatic #2 // Field java/⤦
  11. Ç lang/System.out:Ljava/io/PrintStream;
  12. 11: aload_0
  13. 12: iconst_1
  14. 13: aaload
  15. 14: invokevirtual #4 // Method java/io⤦
  16. Ç /PrintStream.print:(Ljava/lang/String;)V
  17. 17: getstatic #2 // Field java/⤦
  18. Ç lang/System.out:Ljava/io/PrintStream;
  19. 20: ldc #5 // String . How ⤦
  20. Ç are you?
  21. 22: invokevirtual #6 // Method java/io⤦
  22. Ç /PrintStream.println:(Ljava/lang/String;)V
  23. 25: return

aload_0在11行加载,第0个LVA槽的引用(main()函数唯一的参数) iconst_1和aload在行偏移12,13,取得数组第一个元素的引用(从0计数) 字符串对象的引用在栈顶行14行偏移,给println方法。

54.1.34 初始化字符串数组

  1. class Month
  2. {
  3. public static String[] months =
  4. {
  5. "January",
  6. "February",
  7. "March",
  8. "April",
  9. "May",
  10. "June",
  11. "July",
  12. "August",
  13. "September",
  14. "October",
  15. "November",
  16. "December"
  17. };
  18. public String get_month (int i)
  19. {
  20. return months[i];
  21. };
  22. }

935

get_month()函数很简单

  1. public java.lang.String get_month(int);
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=2, locals=2, args_size=2
  5. 0: getstatic #2 // Field months:[⤦
  6. Ç Ljava/lang/String;
  7. 3: iload_1
  8. 4: aaload
  9. 5: areturn

aaload操作数组引用,java字符串是一个对象,所以a_instructiong被用于操作他们.areturn返回字符串对象的引用。

month[]数值是如果初始化的?

  1. static {};
  2. flags: ACC_STATIC
  3. Code:
  4. stack=4, locals=0, args_size=0
  5. 0: bipush 12
  6. 2: anewarray #3 // class java/⤦
  7. Ç lang/String
  8. 5: dup
  9. 6: iconst_0
  10. 7: ldc #4 // String January
  11. 9: aastore
  12. 10: dup
  13. 11: iconst_1
  14. 12: ldc #5 // String ⤦
  15. Ç February
  16. 14: aastore
  17. 15: dup
  18. 16: iconst_2
  19. 17: ldc #6 // String March
  20. 19: aastore
  21. 20: dup
  22. 21: iconst_3
  23. 22: ldc #7 // String April
  24. 24: aastore
  25. 25: dup
  26. 26: iconst_4
  27. 27: ldc #8 // String May
  28. 29: aastore
  29. 30: dup
  30. 31: iconst_5
  31. 32: ldc #9 // String June
  32. 34: aastore
  33. 35: dup
  34. 36: bipush 6
  35. 38: ldc #10 // String July
  36. 40: aastore
  37. 41: dup
  38. 42: bipush 7
  39. 44: ldc #11 // String August
  40. 46: aastore
  41. 47: dup
  42. 48: bipush 8
  43. 50: ldc #12 // String ⤦
  44. Ç September
  45. 52: aastore
  46. 53: dup
  47. 54: bipush 9
  48. 56: ldc #13 // String October
  49. 58: aastore
  50. 59: dup
  51. 60: bipush 10
  52. 62: ldc #14 // String ⤦
  53. Ç November
  54. 64: aastore
  55. 65: dup
  56. 66: bipush 11
  57. 68: ldc #15 // String ⤦
  58. Ç December
  59. 70: aastore
  60. 71: putstatic #2 // Field months:[⤦
  61. Ç Ljava/lang/String;
  62. 74: return

936

937 anewarray 创建一个新数组的引用(a是一个前缀)对象的类型被定义在anewarray操作数中,它在这是“java/lang/string”文本字符串,在这之前的bipush 1L是设置数组的大小。 对于我们再这看到一个新指令dup,他是一个众所周知的堆栈操作的计算机指令。用于复制栈顶的值。(包括了之后的编程语言)它在这是用于复制数组的引用。因为aastore张玲玲 起到弹出堆栈中的数组的作用,但是之后,aastore需要在使用一次,java编译器,最好同dup代替getstatic指令,用于生成之前的每个数组的存贮操作。例如,月份字段。

54.13.5可变参数 可变参数 变长参数函数,实际上使用的就是数组,实际使用的就是数组。

  1. public static void f(int... values)
  2. {
  3. for (int i=0; i<values.length; i++)
  4. System.out.println(values[i]);
  5. }
  6. public static void main(String[] args)
  7. {
  8. f (1,2,3,4,5);
  9. }
  10. public static void f(int...);
  11. flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
  12. Code:
  13. stack=3, locals=2, args_size=1
  14. 0: iconst_0
  15. 1: istore_1
  16. 2: iload_1
  17. 3: aload_0
  18. 4: arraylength
  19. 5: if_icmpge 23
  20. 8: getstatic #2 // Field java/⤦
  21. Ç lang/System.out:Ljava/io/PrintStream;
  22. 11: aload_0
  23. 12: iload_1
  24. 13: iaload
  25. 14: invokevirtual #3 // Method java/io⤦
  26. Ç /PrintStream.println:(I)V
  27. 17: iinc 1, 1
  28. 20: goto 2
  29. 23: return

f()函数,取得一个整数数组,使用的是aload_0 在行偏移3行。取得到了一个数组的大小,等等。

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=4, locals=1, args_size=1
  5. 0: iconst_5
  6. 1: newarray int
  7. 3: dup
  8. 4: iconst_0
  9. 5: iconst_1
  10. 6: iastore
  11. 7: dup
  12. 8: iconst_1
  13. 9: iconst_2
  14. 10: iastore
  15. 11: dup
  16. 12: iconst_2
  17. 13: iconst_3
  18. 14: iastore
  19. 15: dup
  20. 16: iconst_3
  21. 17: iconst_4
  22. 18: iastore
  23. 19: dup
  24. 20: iconst_4
  25. 21: iconst_5
  26. 22: iastore
  27. 23: invokestatic #4 // Method f:([I)V
  28. 26: return

素组在main()函数是构造的,使用newarray指令,被填充慢了之后f()被调用。

939 随便提一句,数组对象并不是在main()中销毁的,在整个java中也没有被析构。因为JVM的垃圾收集齐不是自动的,当他感觉需要的时候。 format()方法是做什么的?它用两个参数作为输入,字符串和数组对象。

  1. public PrintStream format(String format, Object... args⤦)

让我们看一下。

  1. public static void main(String[] args)
  2. {
  3. int i=123;
  4. double d=123.456;
  5. System.out.format("int: %d double: %f.%n", i, d
  6. Ç );
  7. }
  8. public static void main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=7, locals=4, args_size=1
  12. 0: bipush 123
  13. 2: istore_1
  14. 3: ldc2_w #2 // double 123.456⤦
  15. Ç d
  16. 6: dstore_2
  17. 7: getstatic #4 // Field java/⤦
  18. Ç lang/System.out:Ljava/io/PrintStream;
  19. 10: ldc #5 // String int: %d⤦
  20. Ç double: %f.%n
  21. 12: iconst_2
  22. 13: anewarray #6 // class java/⤦
  23. Ç lang/Object
  24. 16: dup
  25. 17: iconst_0
  26. 18: iload_1
  27. 19: invokestatic #7 // Method java/⤦
  28. Ç lang/Integer.valueOf:(I)Ljava/lang/Integer;
  29. 22: aastore
  30. 23: dup
  31. 24: iconst_1
  32. 25: dload_2
  33. 26: invokestatic #8 // Method java/⤦
  34. Ç lang/Double.valueOf:(D)Ljava/lang/Double;
  35. 29: aastore
  36. 30: invokevirtual #9 // Method java/io⤦
  37. Ç /PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object
  38. Ç ;)Ljava/io/PrintStream;
  39. 33: pop
  40. 34: return

940

所以int和double类型是被首先普生为integer和double 对象,被用于方法的值。。。format()方法需要,对象雷翔的对象作为输入,因为integer和double类是继承于根类root。他们适合作为数组输入的元素, 另一方面,数组总是同质的,例如,同一个数组不能含有两种不同的数据类型。不能同时都把integer和double类型的数据同时放入的数组。

数组对象的对象在偏移13行,整型对象被添加到在行偏移22. double对象被添加到数组在29行。

倒数第二的pop指令,丢弃了栈顶的元素,因此,这些return执行,堆栈是的空的(平行)

54.13.6 二位数组

二位数组在java 中是一个数组去引用另外一个数组 让我们来创建二位素组。()

  1. public static void main(String[] args)
  2. {
  3. int[][] a = new int[5][10];
  4. a[1][2]=3;
  5. }
  6. public static void main(java.lang.String[]);
  7. flags: ACC_PUBLIC, ACC_STATIC
  8. Code:
  9. stack=3, locals=2, args_size=1
  10. 0: iconst_5
  11. 1: bipush 10
  12. 3: multianewarray #2, 2 // class "[[I"
  13. 7: astore_1
  14. 8: aload_1
  15. 9: iconst_1
  16. 10: aaload
  17. 11: iconst_2
  18. 12: iconst_3
  19. 13: iastore
  20. 14: return

它创建使用的是multianewarry指令:对象类型和维数作为操作数,数组的大小(10*5),返回到栈中。(使用iconst_5和bipush指令)

行引用在行偏移10加载(iconst_1和aaload)列引用是选择使用iconst_2指令,在行偏移11行。值得写入和设定在12行,iastore在13 行,写入数据元素?

  1. public static int get12 (int[][] in)
  2. {
  3. return in[1][2];
  4. }
  5. public static int get12(int[][]);
  6. flags: ACC_PUBLIC, ACC_STATIC
  7. Code:
  8. stack=2, locals=1, args_size=1
  9. 0: aload_0
  10. 1: iconst_1
  11. 2: aaload
  12. 3: iconst_2
  13. 4: iaload
  14. 5: ireturn

引用数组在行2加载,列的设置是在行3,iaload加载数组。

54.13.7 三维数组 三维数组是,引用一维数组引用一维数组。

  1. public static void main(String[] args)
  2. {
  3. int[][][] a = new int[5][10][15];
  4. a[1][2][3]=4;
  5. get_elem(a);
  6. }
  7. public static void main(java.lang.String[]);
  8. flags: ACC_PUBLIC, ACC_STATIC
  9. Code:
  10. stack=3, locals=2, args_size=1
  11. 0: iconst_5
  12. 1: bipush 10
  13. 3: bipush 15
  14. 5: multianewarray #2, 3 // class "[[[I"
  15. 9: astore_1
  16. 10: aload_1
  17. 11: iconst_1
  18. 12: aaload
  19. 13: iconst_2
  20. 14: aaload
  21. 15: iconst_3
  22. 16: iconst_4
  23. 17: iastore
  24. 18: aload_1
  25. 19: invokestatic #3 // Method ⤦
  26. Ç get_elem:([[[I)I
  27. 22: pop
  28. 23: return

942

它是用两个aaload指令去找right引用。

  1. public static int get_elem (int[][][] a)
  2. {
  3. return a[1][2][3];
  4. }
  5. public static int get_elem(int[][][]);
  6. flags: ACC_PUBLIC, ACC_STATIC
  7. Code:
  8. stack=2, locals=1, args_size=1
  9. 0: aload_0
  10. 1: iconst_1
  11. 2: aaload
  12. 3: iconst_2
  13. 4: aaload
  14. 5: iconst_3
  15. 6: iaload
  16. 7: ireturn

943

53.13.8总结 在java中可能出现栈溢出吗?不可能,数组长度实际就代表有多少个对象,数组的边界是可控的,而发生越界访问的情况时,会抛出异常。

54.15 异常 让我们稍微修改一下,月处理的那个例子(在932页的54.13.4)

清单 54.10: IncorrectMonthException.java

  1. public class IncorrectMonthException extends Exception
  2. {
  3. private int index;
  4. public IncorrectMonthException(int index)
  5. {
  6. this.index = index;
  7. }
  8. public int getIndex()
  9. {
  10. return index;
  11. }
  12. }

清单 54.11: Month2.java

  1. class Month2
  2. {
  3. public static String[] months =
  4. {
  5. "January",
  6. "February",
  7. "March",
  8. "April",
  9. "May",
  10. "June",
  11. "July",
  12. "August",
  13. "September",
  14. "October",
  15. "November",
  16. "December"
  17. };
  18. public static String get_month (int i) throws
  19. Ç IncorrectMonthException
  20. {
  21. if (i<0 || i>11)
  22. throw new IncorrectMonthException(i);
  23. return months[i];
  24. };
  25. public static void main (String[] args)
  26. {
  27. try
  28. {
  29. System.out.println(get_month(100));
  30. }
  31. catch(IncorrectMonthException e)
  32. {
  33. System.out.println("incorrect month ⤦
  34. Ç index: "+ e.getIndex());
  35. e.printStackTrace();
  36. }
  37. };
  38. }

本质上,IncorrectMonthExceptinClass类只是做了对象构造,还有访问器方法。 IncorrectMonthExceptinClass是继承于Exception类,所以,IncorrectMonth类构造之前,构造父类Exception,然后传递整数给IncorrectMonthException类作为唯一的属性值。

  1. public IncorrectMonthException(int);
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=2, locals=2, args_size=2
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/⤦
  7. Ç lang/Exception."<init>":()V
  8. 4: aload_0
  9. 5: iload_1
  10. 6: putfield #2 // Field index:I
  11. 9: return

getIndex()只是一个访问器,引用到IncorrectMothnException类,被传到LVA的0槽(this指针),用aload_0指令取得, 用getfield指令取得对象的整数值,用ireturn指令将其返回。

  1. public int getIndex();
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: aload_0
  6. 1: getfield #2 // Field index:I
  7. 4: ireturn

现在来看下month.class的get_month方法。

清单 54.12: Month2.class

  1. public static java.lang.String get_month(int) throws
  2. Ç IncorrectMonthException;
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=3, locals=1, args_size=1
  6. 0: iload_0
  7. 1: iflt 10
  8. 4: iload_0
  9. 5: bipush 11
  10. 7: if_icmple 19
  11. 10: new #2 // class ⤦
  12. Ç IncorrectMonthException
  13. 13: dup
  14. 14: iload_0
  15. 15: invokespecial #3 // Method ⤦
  16. Ç IncorrectMonthException."<init>":(I)V
  17. 18: athrow
  18. 19: getstatic #4 // Field months:[⤦
  19. Ç Ljava/lang/String;
  20. 22: iload_0
  21. 23: aaload
  22. 24: areturn

949

iflt 在行偏移1 ,如果小于的话,

这种情况其实是无效的索引,在行偏移10创建了一个对象,对象类型是作为操作书传递指令的。(这个IncorrectMonthException的构造届时,下标整数是被通过TOS传递的。行15偏移) 时间流程走到了行18偏移,对象已经被构造了,现在athrow指令取得新构对象的引用,然后发信号给JVM去找个合适的异常句柄。

athrow指令在这个不返回到控制流,行19偏移的其他的个基本模块,和异常无关,我们能得到到行7偏移。 句柄怎么工作? main()在inmonth2.class

清单 54.13: Month2.class

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=3, locals=2, args_size=1
  5. 0: getstatic #5 // Field java/⤦
  6. Ç lang/System.out:Ljava/io/PrintStream;
  7. 3: bipush 100
  8. 5: invokestatic #6 // Method ⤦
  9. Ç get_month:(I)Ljava/lang/String;
  10. 8: invokevirtual #7 // Method java/io⤦
  11. Ç /PrintStream.println:(Ljava/lang/String;)V
  12. 11: goto 47
  13. 14: astore_1
  14. 15: getstatic #5 // Field java/⤦
  15. Ç lang/System.out:Ljava/io/PrintStream;
  16. 18: new #8 // class java/⤦
  17. Ç lang/StringBuilder
  18. 21: dup
  19. 22: invokespecial #9 // Method java/⤦
  20. Ç lang/StringBuilder."<init>":()V
  21. 25: ldc #10 // String ⤦
  22. Ç incorrect month index:
  23. 27: invokevirtual #11 // Method java/⤦
  24. Ç lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/⤦
  25. Ç StringBuilder;
  26. 30: aload_1
  27. 31: invokevirtual #12 // Method ⤦
  28. Ç IncorrectMonthException.getIndex:()I
  29. 34: invokevirtual #13 // Method java/⤦
  30. Ç lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  31. 37: invokevirtual #14 // Method java/⤦
  32. Ç lang/StringBuilder.toString:()Ljava/lang/String;
  33. 40: invokevirtual #7 // Method java/io⤦
  34. Ç /PrintStream.println:(Ljava/lang/String;)V
  35. 43: aload_1
  36. 44: invokevirtual #15 // Method ⤦
  37. Ç IncorrectMonthException.printStackTrace:()V
  38. 47: return
  39. Exception table:
  40. from to target type
  41. 0 11 14 Class IncorrectMonthException

950 这是一个异常表,在行偏移0-11(包括)行,一个IncorrectinMonthException异常可能发生,如果发生,控制流到达14行偏移,确实main程序在11行偏移结束,在14行异常开始, 没有进入此区域条件(condition/uncondition)设定,是不可能到打这个位置的。(PS:就是没有异常捕获的设定,就不会有异常流被调用执行。)

但是JVM会传递并覆盖执行这个异常case。 第一个astore_1(在行偏移14)取得,将到来的异常对象的引用,存储在LVA的槽参数1之后。getIndex()方法(这个异常对象) 会被在31行偏移调用。引用当前的异常对象,是在30行偏移之前。 所有的这些代码重置都是字符串操作代码:第一个整数值使用的是getIndex()方法,被转换成字符串使用的是toString()方法,它会和“正确月份下标”的文本字符来链接(像我们之前考虑的那样)。 println()和printStackTrace(1)会被调用,PrintStackTrace(1)调用 结束之后,异常被捕获,我们可以处理正常的函数,在47行偏移,return结束main()函数 , 如果没有发生异常,不会执行任何的代码。

这有个例子,IDA是如何显示异常范围:

清单54.14 我从我的计算机中找到 random.class 这个文件

  1. .catch java/io/FileNotFoundException from met001_335 to
  2. Ç met001_360\
  3. using met001_360
  4. .catch java/io/FileNotFoundException from met001_185 to
  5. Ç met001_214\
  6. using met001_214
  7. .catch java/io/FileNotFoundException from met001_181 to
  8. Ç met001_192\
  9. using met001_195
  10. 951
  11. CHAPTER 54. JAVA 54.16. CLASSES
  12. .catch java/io/FileNotFoundException from met001_155 to
  13. Ç met001_176\
  14. using met001_176
  15. .catch java/io/FileNotFoundException from met001_83 to
  16. Ç met001_129 using \
  17. met001_129
  18. .catch java/io/FileNotFoundException from met001_42 to
  19. Ç met001_66 using \
  20. met001_69
  21. .catch java/io/FileNotFoundException from met001_begin to
  22. Ç met001_37\
  23. using met001_37

[校准到这结束。]

54.17 简单的补丁。

54.17.1 第一个例子

让我们进入一个简单的修补任务。

  1. public class nag
  2. {
  3. public static void nag_screen()
  4. {
  5. System.out.println("This program is not ⤦
  6. Ç registered");
  7. };
  8. public static void main(String[] args)
  9. {
  10. System.out.println("Greetings from the mega-⤦
  11. Ç software");
  12. nag_screen();
  13. }
  14. }

我们如何去除“This program is registered”的打印输出.

最会在IDA中加载.class文件。

955

清单54.1: IDA

我们修补一下函数的第一个byte在177(返回指令操作码)

Figure 54.2 : IDA

这个在JDK1.7中不工作

  1. Exception in thread "main" java.lang.VerifyError: Expecting a
  2. Ç stack map frame
  3. Exception Details:
  4. Location:
  5. nag.nag_screen()V @1: nop
  6. Reason:
  7. Error exists in the bytecode
  8. Bytecode:
  9. 0000000: b100 0212 03b6 0004 b1
  10. at java.lang.Class.getDeclaredMethods0(Native Method)
  11. at java.lang.Class.privateGetDeclaredMethods(Class.java
  12. Ç :2615)
  13. at java.lang.Class.getMethod0(Class.java:2856)
  14. at java.lang.Class.getMethod(Class.java:1668)
  15. at sun.launcher.LauncherHelper.getMainMethod(⤦
  16. Ç LauncherHelper.java:494)
  17. at sun.launcher.LauncherHelper.checkAndLoadMain(⤦
  18. Ç LauncherHelper.java:486)

956 也许,JVM有一些其他检查,关联到栈映射。 好的,我们修补成不同的,去掉nag()函数调用。

清单:54.5 IDA NOP的操作码是0: 这个可以了!

54.17.2第二个例子

现在是另外一个简单的crackme例子。

  1. public class password
  2. {
  3. public static void main(String[] args)
  4. {
  5. System.out.println("Please enter the password")⤦
  6. Ç ;
  7. String input = System.console().readLine();
  8. if (input.equals("secret"))
  9. System.out.println("password is correct⤦
  10. Ç ");
  11. 957
  12. CHAPTER 54. JAVA 54.17. SIMPLE PATCHING
  13. else
  14. System.out.println("password is not ⤦
  15. Ç correct");
  16. }
  17. }

957

图54.4:IDA 我们看ifeq指令是怎么工作的,他的名字的意思是如果等于。 这是不恰当的,我更愿意命名if (ifz if zero) 如果栈顶值是0,他就会跳转,在我们这个例子,如果密码 不正确他就跳转。(equal方法返回的是0) 首先第一个方案就是修该这个指令… iefq是两个bytes的操作码 编码和跳转偏移,让这个指令定制,我们必须设定byte3 3byte(因为3是要添加当前地址结果,总是跳转同下一条指令) 因为ifeq的指令长度就是3bytes.

958 图54.5IDA

这个在JDK1.7中不工作

  1. Exception in thread "main" java.lang.VerifyError: Expecting a
  2. Ç stackmap frame at branch target 24
  3. Exception Details:
  4. Location:
  5. password.main([Ljava/lang/String;)V @21: ifeq
  6. Reason:
  7. Expected stackmap frame at this location.
  8. Bytecode:
  9. 0000000: b200 0212 03b6 0004 b800 05b6 0006 4c2b
  10. 0000010: 1207 b600 0899 0003 b200 0212 09b6 0004
  11. 0000020: a700 0bb2 0002 120a b600 04b1
  12. Stackmap Table:
  13. append_frame(@35,Object[#20])
  14. same_frame(@43)
  15. at java.lang.Class.getDeclaredMethods0(Native Method)
  16. at java.lang.Class.privateGetDeclaredMethods(Class.java
  17. Ç :2615)
  18. at java.lang.Class.getMethod0(Class.java:2856)
  19. at java.lang.Class.getMethod(Class.java:1668)
  20. at sun.launcher.LauncherHelper.getMainMethod(⤦
  21. Ç LauncherHelper.java:494)
  22. 959
  23. CHAPTER 54. JAVA 54.18. SUMMARY
  24. at sun.launcher.LauncherHelper.checkAndLoadMain(⤦
  25. Ç LauncherHelper.java:486)

不用说了,它工作在JRE1.6 我也尝试把所有的3 ifeq的所有操作码都用0替换(NOP),它仍然会工作,好,可能没有更多的堆栈映射在JRE1.7中被检查出来。

好的,我替换整个equal方法调用,使用icore_1指令加NOPS的修改。

(TOS)栈顶总是1,当ifeq指令被执行…所以ifeq也不会被执行。

可以了。

54.18总结

960 和C/C+比较java少了一些什么? 结构体:使用类 联合:使用类继承。 无符号数据类型,多说一句,还有一些在Java中实现的加密算法的硬编码。 函数指针。 54.2 返回一个值

可能最简单的java函数就是返回一些值,oh,并且我们必须注意,一边情况下,在java中没有孤立存在的函数,他们是“方法”(method),每个方法都是被关联到某些类,所以方法不会被定义在类外面, 但是我还是叫他们“函数” (function),我这么用。

  1. public class ret
  2. {
  3. public static int main(String[] args)
  4. {
  5. return 0;
  6. }
  7. }

编译它。

  1. javac ret.java

。。。使用Java标准工具反编译。

  1. javap -c -verbose ret.class

会得到结果:

  1. public static int main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: iconst_0
  6. 1: ireturn

对于java开发者在编程中,0是使用频率最高的常量。 因为区分短一个短字节的 iconst_0指令入栈0,iconst_1指令(入栈),iconst_2等等,直到iconst5。也可以有iconst_m1, 推送-1。

就像在MIPS中,分离一个寄存器给0常数:3.5.2 在第三页。

栈在JVM中用于在函数调用时,传参和传返回值。因此, iconst_0是将0入栈,ireturn指令,(i就是integer的意思。)是从栈顶返回整数值。

[校准到这,未完待续…]

让我们写一个简单的例子, 现在我们返回1234:

  1. public class ret
  2. {
  3. public static int main(String[] args)
  4. {
  5. return 1234;
  6. }
  7. }

我们得到:

清单: 54.2:jdk1.7(节选) public static int main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: sipush 1234 3: ireturn

sipush(shot integer)如栈值是1234,slot的名字以为着一个16bytes值将会入栈。 sipush(短整型) 1234数值确认时候16-bit值。

  1. public class ret
  2. {
  3. public static int main(String[] args)
  4. {
  5. return 12345678;
  6. }
  7. }

更大的值是什么?

清单 54.3 常量区

  1. ...
  2. #2 = Integer 12345678
  3. ...

5栈顶

  1. public static int main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATI
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: ldc #2 // int 12345678
  6. 2: ireturn

907

操作码 JVM的指令码操作码不可能编码成32位数,开发者放弃这种可能。因此,32位数字12345678是被存储在一个叫做常量区的地方。让我们说(大多数被使用的常数(包括字符,对象等等车)) 对我们而言。

对JVM来说传递常量不是唯一的,MIPS ARM和其他的RISC CPUS也不可能把32位操作编码成32位数字,因此 RISC CPU(包括MIPS和ARM)去构造一个值需要一系列的步骤,或是他们保存在数据段中: 28。3 在654页.291 在695页。

MIPS码也有一个传统的常量区,literal pool(原语区) 这个段被叫做“lit4”(对于32位单精度浮点数常数存储) 和lit8(64位双精度浮点整数常量区)

布尔型

  1. public class ret
  2. {
  3. public static boolean main(String[] args)
  4. {
  5. return true;
  6. }
  7. }
  8. public static boolean main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=1, locals=1, args_size=1
  12. 0: iconst_1

这个JVM字节码是不同于返回的整数学 ,32位数据,在形参中被当成逻辑值使用。像C/C++,但是不能像使用整型或是viceversa返回布尔型,类型信息被存储在类文件中,在运行时检查。

16位短整型也是一样。

908

  1. public class ret
  2. {
  3. public static short main(String[] args)
  4. {
  5. return 1234;
  6. }
  7. }
  8. public static short main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=1, locals=1, args_size=1
  12. 0: sipush 1234
  13. 3: ireturn

还有char 字符型?

  1. public class ret
  2. {
  3. public static char main(String[] args)
  4. {
  5. return 'A';
  6. }
  7. }
  8. public static char main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=1, locals=1, args_size=1
  12. 0: bipush 65
  13. 2: ireturn

bipush 的意思“push byte”字节入栈,不必说java的char是16位UTF16字符,和short 短整型相等,单ASCII码的A字符是65,它可能使用指令传输字节到栈。

让我们是试一下byte。

  1. public class retc
  2. {
  3. public static byte main(String[] args)
  4. {
  5. return 123;
  6. }
  7. }
  8. public static byte main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=1, locals=1, args_size=1
  12. 0: bipush 123
  13. 2: ireturn

909

也许会问,位什么费事用两个16位整型当32位用?为什么char数据类型和短整型类型还使用char.

答案很简单,为了数据类型的控制和代码的可读性。char也许本质上short相同,但是我们快速的掌握它的占位符,16位的UTF字符,并且不像其他的integer值符。使用 short,为各位展现一下变量的范围被限制在16位。在需要的地方使用boolean型也是一个很好的主意。代替C样式的int也是为了相同的目的。

在java中integer的64位数据类型。

  1. public class ret3
  2. {
  3. public static long main(String[] args)
  4. {
  5. return 1234567890123456789L;
  6. }
  7. }

清单54.4常量区

  1. ...
  2. #2 = Long 1234567890123456789l
  3. ...
  4. public static long main(java.lang.String[]);
  5. flags: ACC_PUBLIC, ACC_STATIC
  6. Code:
  7. stack=2, locals=1, args_size=1
  8. 0: ldc2_w #2 // long ⤦
  9. Ç 1234567890123456789l
  10. 3: lreturn

64位数也被在存储在常量区,ldc2_w 加载它,lreturn返回它。 ldc2_w指令也是从内存常量区中加载双精度浮点数。(同样占64位)

  1. public class ret
  2. {
  3. public static double main(String[] args)
  4. {
  5. return 123.456d;
  6. }
  7. }

清单54.5常量区

  1. ...
  2. #2 = Double 123.456d
  3. ...
  4. public static double main(java.lang.String[]);
  5. flags: ACC_PUBLIC, ACC_STATIC
  6. Code:
  7. stack=2, locals=1, args_size=1
  8. 0: ldc2_w #2 // double 123.456⤦
  9. Ç d
  10. 3: dreturn

dreturn 代表 “return double”

最后,单精度浮点数:

  1. public class ret
  2. {
  3. public static float main(String[] args)
  4. {
  5. return 123.456f;
  6. }
  7. }

清单54.6 常量区

  1. ...
  2. #2 = Float 123.456f
  3. ...
  4. public static float main(java.lang.String[]);
  5. flags: ACC_PUBLIC, ACC_STATIC
  6. Code:
  7. stack=1, locals=1, args_size=1
  8. 0: ldc #2 // float 123.456f
  9. 2: freturn

此处的ldc指令使用和32位整型数据一样,从常量区中加载。freturn 的意思是“return float”

那么函数还能返回什么呢?

911

  1. public class ret
  2. {
  3. public static void main(String[] args)
  4. {
  5. return;
  6. }
  7. }
  8. public static void main(java.lang.String[]);
  9. flags: ACC_PUBLIC, ACC_STATIC
  10. Code:
  11. stack=0, locals=1, args_size=1
  12. 0: return

这以为着,使用return控制指令确没有返回实际的值,知道这一点就非常容易的从最后一条指令中演绎出函数(或是方法)的返回类型。

54.4 JVM内存模型

X86和其他低级环境系统使用栈传递参数和存储本地变量,JVM稍微有些不同。

主要体现在: 本地变量数组(LVA)被用于存储到来函数的参数和本地变量。iload_0指令是从其中加载值,istore存储值在其中,首先,函数参数到达:开始从0 或者1(如果0参被this指针用。),那么本地局部变量被分配。

每个槽子的大小都是32位,因此long和double数据类型都占两个槽。

操作数栈(或只是“栈”),被用于在其他函数调用时,计算和传递参数。不像低级X86的环境,它不能去访问栈,而又不明确的使用pushes和pops指令,进行出入栈操作。

54.6 调用beep()函数 这可能是最简单的,不使用参数的调用两个函数。

  1. public static void main(String[] args)
  2. {
  3. java.awt.Toolkit.getDefaultToolkit().beep();
  4. };
  5. public static void main(java.lang.String[]);
  6. flags: ACC_PUBLIC, ACC_STATIC
  7. Code:
  8. stack=1, locals=1, args_size=1
  9. 0: invokestatic #2 // Method java/⤦
  10. Ç awt/Toolkit.getDefaultToolkit:()Ljava/awt/Toolkit;
  11. 3: invokevirtual #3 // Method java/⤦
  12. Ç awt/Toolkit.beep:()V
  13. 6: return

首先,invokestatic在0行偏移调用javaawt.toolkit. getDefaultTookKit()函数,返回toolkit类对象的引用,invokedvirtualIFge指令在3行偏移,调用这个类的beep()方法。

54.8 条件跳转 让我们进入条件跳转

  1. public class abs
  2. {
  3. public static int abs(int a)
  4. {
  5. if (a<0)
  6. return -a;
  7. return a;
  8. }
  9. }
  10. public static int abs(int);
  11. flags: ACC_PUBLIC, ACC_STATIC
  12. Code:
  13. stack=1, locals=1, args_size=1
  14. 0: iload_0
  15. 1: ifge 7
  16. 4: iload_0
  17. 5: ineg
  18. 6: ireturn
  19. 7: iload_0
  20. 8: ireturn

921

ifge跳转到7行偏移,如果栈顶的值大于等于0,别忘了,任何IFXX指令从栈中pop出栈值(用于进行比较)

另外一个例子

  1. public static int min (int a, int b)
  2. {
  3. if (a>b)
  4. return b;
  5. return a;
  6. }

我们得到的是:

  1. public static int min(int, int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=2, locals=2, args_size=2
  5. 0: iload_0
  6. 1: iload_1
  7. 2: if_icmple 7
  8. 5: iload_1
  9. 6: ireturn
  10. 7: iload_0
  11. 8: ireturn

if_icmple出栈两个值并比较他们,如果第三个子值比第一个值小(或者等于)发生跳转到行偏移7.

当我们定义max()函数。

  1. public static int max (int a, int b)
  2. {
  3. if (a>b)
  4. return a;
  5. return b;
  6. }

。。。结果代码是是一样的,但是最后两个iload指令(行偏移5和行偏移7)被跳转了。

  1. public static int max(int, int);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=2, locals=2, args_size=2
  5. 0: iload_0
  6. 1: iload_1
  7. 2: if_icmple 7
  8. 5: iload_0
  9. 6: ireturn
  10. 7: iload_1
  11. 8: ireturn

922 更复杂的例子。。

  1. public class cond
  2. {
  3. public static void f(int i)
  4. {
  5. if (i<100)
  6. System.out.print("<100");
  7. if (i==100)
  8. System.out.print("==100");
  9. if (i>100)
  10. System.out.print(">100");
  11. if (i==0)
  12. System.out.print("==0");
  13. }
  14. }
  15. public static void f(int);
  16. flags: ACC_PUBLIC, ACC_STATIC
  17. Code:
  18. stack=2, locals=1, args_size=1
  19. 0: iload_0
  20. 1: bipush 100
  21. 3: if_icmpge 14
  22. 6: getstatic #2 // Field java/⤦
  23. Ç lang/System.out:Ljava/io/PrintStream;
  24. 9: ldc #3 // String <100
  25. 11: invokevirtual #4 // Method java/io⤦
  26. Ç /PrintStream.print:(Ljava/lang/String;)V
  27. 14: iload_0
  28. 15: bipush 100
  29. 17: if_icmpne 28
  30. 20: getstatic #2 // Field java/⤦
  31. Ç lang/System.out:Ljava/io/PrintStream;
  32. 23: ldc #5 // String ==100
  33. 25: invokevirtual #4 // Method java/io⤦
  34. Ç /PrintStream.print:(Ljava/lang/String;)V
  35. 28: iload_0
  36. 29: bipush 100
  37. 31: if_icmple 42
  38. 34: getstatic #2 // Field java/⤦
  39. Ç lang/System.out:Ljava/io/PrintStream;
  40. 37: ldc #6 // String >100
  41. 39: invokevirtual #4 // Method java/io⤦
  42. Ç /PrintStream.print:(Ljava/lang/String;)V
  43. 42: iload_0
  44. 43: ifne 54
  45. 46: getstatic #2 // Field java/⤦
  46. Ç lang/System.out:Ljava/io/PrintStream;
  47. 49: ldc #7 // String ==0
  48. 51: invokevirtual #4 // Method java/io⤦
  49. Ç /PrintStream.print:(Ljava/lang/String;)V
  50. 54: return

923 if_icmpge出栈两个值,并且比较它们,如果第的二个值大于第一个,发生跳转到行偏移14,if_icmpne和if_icmple做的工作类似,但是使用不同的判断条件。

在行偏移43的ifne指令,它的名字不是很恰当,我要愿意把它命名为ifnz

如果栈定的值不是0跳转,但是这是怎么做的,总跳转到行偏移54,如果输入的值不是另,如果是0,执行流程进入行偏移46,“==”字符串被打印。

N.BJVM没有无符号数据类型,所以,比较指令的操作数,只有还有符号整数值。