从字节扩展到Int(Marshmallow bug)

时间:2016-06-01 15:25:19

标签: android android-6.0-marshmallow

对于这个长期问题提前抱歉,但一切都应该是直截了当的,明确发生了什么,谢谢你看看。请注意,这实际上不是代码,只是伪代码来理解应用程序的实现。

  

问题

字节不会扩大到真正的数值。注意:level = -1表示尚未开始的游戏。 level == 24表示游戏的结束。

第1类

private byte level = -1;
private byte stage = 0;

@NonNull private final Stages[][] stages = new Stages[25][13];

public byte getLevel () {
   return level;
}  

public void nextLevel () {
// expect: 0 
   level++;

// stages[  128][    0] (ArrayIndexOutOfBounds)
   stages[level][stage] = new Stage();
}

第2类扩展第1类

@NonNull private final byte[][] values = new byte[25][4];

public byte getScore (final byte level, final byte player) {
   //     values[  255][     2] (ArrayIndexOutOfBounds)
   return values[level][player];
}

// expecting: -1  >= 0 (false)
// runtime:   255 >= 0 (true)
if (Class1.getLevel() >= 0)
    getScore(Class1.getLevel(), 2);
  

二进制

8位(字节)

-1   == 1111 1111
-128 == 1000 0000
 127 == 0111 1111

32位(整数)

-1   == 1111 1111 1111 1111 1111 1111 1111 1111
 127 == 0000 0000 0000 0000 0000 0000 0111 1111
 128 == 0000 0000 0000 0000 0000 0000 1000 0000
 255 == 0000 0000 0000 0000 0000 0000 1111 1111
  

工作成就

使用包装类

public Byte level = -1;
  

问题

我理解问题是数字的二进制表示在从byte扩展到int时直接使用。我的数字实际上是从8位1111 1111到32位0000 0000 0000 0000 0000 0000 1111 1111。我的问题是为什么Java(或者为什么不在这种情况/环境中)在扩展时将数字转换为真正的数值而不是仅仅将填充零填充到原始二进制表示。

这似乎只发生在负数上,我假设因为正数在加宽之前和之后具有相同的位表示。

为什么我的号码从8位1111 1111变为32位1111 1111 1111 1111 1111 1111 1111 1111?为什么后缀增量创建值为128 ..?除了我目前坚持使用的问题之外,还有更好的解决方案吗?

我不想在不知道下划线问题的情况下使用该解决方案,因为问题可以悄悄地(无错误地运行)破坏我的应用程序算法;如果有人可以向我解释这个问题,我们非常感激。

谢谢,杰伊

  

当前的工作环境

JDK 1.8.076

操作系统 X El Capitan

Android Studio 2.2预览2

buildToolsVersion '23 .0.3'

classpath 'com.android.tools.build:grad:2.2.0 -alpha2'

模拟器 Nexus 6P API 23 x86

  

结论

我能够将问题缩小到仅限Android 23(Marshmallow)设备。我向谷歌公司报告了这个错误。感谢大家的帮助,我只会向Android 23(Marshmallow)用户发出警告,因为该错误没有出现在Android N,Android 22及更低版本中。

2 个答案:

答案 0 :(得分:3)

为什么不将带符号的字节转换为无符号?

public static int signedByteToInt(byte b) {
    return b & 0xFF;
}

这里确保你是带符号字节的表示样本:

-3 - 11111101
-2 - 11111110
-1 - 11111111
0  - 00000000
1  - 00000001
2  - 00000010
3  - 00000011

当你使用byte作为int时,java将无论如何将此字节表示为已签名的一个,因此从11111111(-1)您将得到11111111 11111111 11111111 11111111(-1)并且我在这里看不到任何问题。

请记住:

from -1 to -128 is from 11111111 to 10000000
and from 0 to 127 is from 00000000 to 01111111

并在将其用作int时进行正确的转换。

所以低于零的表示就像向中间的反向

顺便说一下,这不仅仅是在java中)

这里:

if (Class1.getLevel() >= 0)

你将字节与整数进行比较,尝试制作:

if (Class1.getLevel() >= (byte)0)

并感到高兴:)

答案 1 :(得分:2)

杰伊,我看到了你的代码,我检查了Java语言的文档。

对我而言,您描述的行为如下:

private byte level = -1;
private byte stage = 0;

将字节初始化为-1和0。 之后,您拨打nextLevel()并期望它为初始stage[0][0],但相反,您会收到超出范围的"例外。所以,永远不会达到第一级。

public void nextLevel() {
// expect: 0
    level++;

// stages[  128][    0] (ArrayIndexOutOfBounds)
    stages[level][stage] = new Stage();
}

如果样本中未显示的代码不会产生产生这种情况的副作用,则以下代码是一个工作样本,应该足以揭示问题:

package com.bytemagic;

import com.sun.istack.internal.NotNull;

/**
 * Created by thst on 01.06.2016.
 */
public class ByteMagic {

    private class Stage {
    }

    private byte level = -1;
    private byte stage = 0;

    @NotNull
    private final Stage[][] stages = new Stage[25][13];

    public byte getLevel() {
        return level;
    }

    public void nextLevel() {
    // expect: 0
        level++;

    // stages[  128][    0] (ArrayIndexOutOfBounds)
        stages[level][stage] = new Stage();
    }


    public static void main(String... args) {
        ByteMagic me = new ByteMagic();

        me.nextLevel();
        System.out.println(Integer.toHexString(me.getLevel()));
    }
}

我用JDK8u66,JDK8u77尝试了这段代码。这不会暴露任何问题。 字节level将正确地增加到0并且stages正确初始化。

请您尝试在您的计算机和设置上运行该代码吗?这段代码是否暴露了这个问题?

毕竟:level不能容纳128,它可以容纳-128,但这不是你所描述的。

level++会做各种有趣的转换。根据文档,要计算增量,1level将转换为整数,如果需要适当加宽(所以0xff - > 0xffffffff),然后添加1,得到0x0(int) 。然后将其缩小回0x0(字节)并写入level。 (https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2

阵列访问使用稍微不同的转换规则。数组访问要求索引的类型为整数。从byte到int的转换使用一元数字促销规则,当使用一元减号时可以产生有趣的转换效果,但对于您的情况,字节值为0,不会发生任何壮观或意外。

(供参考:https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.6.1https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.4以及https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.10.3

所以,我无法用给出的代码示例重现失败,我的怀疑是这个方向,这是我看不到的代码的一些副作用,或者它是你使用的JDK或环境中的错误( Dalvik VM)。