如何安全地将 Java 字节用作无符号字符?
How can I safely use a Java byte as an unsigned char?
我正在将一些使用大量位操作的 C 代码移植到 Java。 C 代码在 int 为 32 位宽而 char 为 8 位宽的假设下运行。其中有断言可以检查这些假设是否有效。
我已经接受了我必须使用 long
代替 unsigned int
的事实。但是我可以安全地使用 byte
来替代 unsigned char
吗?
它们仅代表字节,但我已经 运行 了解这个奇怪的事件:(data
在 C 中是一个 unsigned char *
而在 [=36 中是一个 byte[]
=]):
/* C */
uInt32 c = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
/* Java */
long a = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) & 0xffffffff;
long b = ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
((data[2] & 0xff) << 8) | (data[3] & 0xff) & 0xffffffff;
你会认为左移操作是安全的。但是由于 Java、a
和 b
中奇怪的一元提升规则,如果 data
中的某些字节是 "negative"(b
给出了正确的结果)。
我还应该注意哪些 "gotchas"?我真的不想在这里使用short
。
您可以安全地使用 byte
来表示 0 到 255 之间的值,如果您确保在计算中使用它之前将其值与 255(或 0xFF)按位与。这会将其提升为 int
,并确保提升的值介于 0 和 255 之间。
否则,使用符号扩展,整数提升将导致 int
值介于 -128 和 127 之间。 -127 作为 byte
(十六进制 0x81)将变为 -127 作为 int
(十六进制 0xFFFFFF81)。
所以你可以这样做:
long a = (((data[0] & 255) << 24) | ((data[1] & 255) << 16) | ((data[2] & 255) << 8) | (data[3] & 255)) & 0xffffffff;
请注意,第一个 & 255
在这里是不必要的,因为后面的步骤无论如何都会屏蔽掉额外的位 (& 0xffffffff
)。但始终包含它可能是最简单的。
... can I safely use byte
as a replacement for unsigned char
?
如您所见,并不是真的...不。
根据Oracle Java documentation,byte
是一个带符号的整数类型,尽管它有 256 个不同的值(由于明确的范围规范 "It has a minimum value of -128 and a maximum value of 127 (inclusive)" 来自文档)有一些值 C 中的 unsigned char
可以存储,而 Java 中的 byte
不能(反之亦然)。
这解释了您遇到的问题。但是,问题的严重程度尚未在您的 8 位字节实现中得到充分证明。
What other "gotchas" should I be aware of?
虽然 Java 中的 byte
需要仅支持 -128 和 127 之间(包括)之间的值,但 Cs unsigned char
具有最大值(UCHAR_MAX
) 这取决于用于表示它的位数(CHAR_BIT
;至少 8)。所以当CHAR_BIT
大于8时,unsigned char
可以存储超过255的额外值。
总而言之,在 Java 的世界中,a byte
实际上应该称为 octet
(一组八位),而在 C 中 a byte (char
, signed char
, unsigned char
)是一组至少(可能多于)八位。
没有。它们不等价。我认为您也不会在 Java 中找到等效类型;它们都是 固定宽度 。您可以安全地使用 Java 中的 byte
作为 C 中 int8_t
的等价物,但是(除了 int8_t
不需要存在于 C 中,除非 CHAR_BIT == 8
).
至于陷阱,你的 C 代码中也有一些。假设 data[0]
在 INT_MAX == 32767
.
的任何系统上都是 unsigned char
、data[0] << 24
is undefined behaviour
我正在将一些使用大量位操作的 C 代码移植到 Java。 C 代码在 int 为 32 位宽而 char 为 8 位宽的假设下运行。其中有断言可以检查这些假设是否有效。
我已经接受了我必须使用 long
代替 unsigned int
的事实。但是我可以安全地使用 byte
来替代 unsigned char
吗?
它们仅代表字节,但我已经 运行 了解这个奇怪的事件:(data
在 C 中是一个 unsigned char *
而在 [=36 中是一个 byte[]
=]):
/* C */
uInt32 c = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
/* Java */
long a = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) & 0xffffffff;
long b = ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
((data[2] & 0xff) << 8) | (data[3] & 0xff) & 0xffffffff;
你会认为左移操作是安全的。但是由于 Java、a
和 b
中奇怪的一元提升规则,如果 data
中的某些字节是 "negative"(b
给出了正确的结果)。
我还应该注意哪些 "gotchas"?我真的不想在这里使用short
。
您可以安全地使用 byte
来表示 0 到 255 之间的值,如果您确保在计算中使用它之前将其值与 255(或 0xFF)按位与。这会将其提升为 int
,并确保提升的值介于 0 和 255 之间。
否则,使用符号扩展,整数提升将导致 int
值介于 -128 和 127 之间。 -127 作为 byte
(十六进制 0x81)将变为 -127 作为 int
(十六进制 0xFFFFFF81)。
所以你可以这样做:
long a = (((data[0] & 255) << 24) | ((data[1] & 255) << 16) | ((data[2] & 255) << 8) | (data[3] & 255)) & 0xffffffff;
请注意,第一个 & 255
在这里是不必要的,因为后面的步骤无论如何都会屏蔽掉额外的位 (& 0xffffffff
)。但始终包含它可能是最简单的。
... can I safely use
byte
as a replacement forunsigned char
?
如您所见,并不是真的...不。
根据Oracle Java documentation,byte
是一个带符号的整数类型,尽管它有 256 个不同的值(由于明确的范围规范 "It has a minimum value of -128 and a maximum value of 127 (inclusive)" 来自文档)有一些值 C 中的 unsigned char
可以存储,而 Java 中的 byte
不能(反之亦然)。
这解释了您遇到的问题。但是,问题的严重程度尚未在您的 8 位字节实现中得到充分证明。
What other "gotchas" should I be aware of?
虽然 Java 中的 byte
需要仅支持 -128 和 127 之间(包括)之间的值,但 Cs unsigned char
具有最大值(UCHAR_MAX
) 这取决于用于表示它的位数(CHAR_BIT
;至少 8)。所以当CHAR_BIT
大于8时,unsigned char
可以存储超过255的额外值。
总而言之,在 Java 的世界中,a byte
实际上应该称为 octet
(一组八位),而在 C 中 a byte (char
, signed char
, unsigned char
)是一组至少(可能多于)八位。
没有。它们不等价。我认为您也不会在 Java 中找到等效类型;它们都是 固定宽度 。您可以安全地使用 Java 中的 byte
作为 C 中 int8_t
的等价物,但是(除了 int8_t
不需要存在于 C 中,除非 CHAR_BIT == 8
).
至于陷阱,你的 C 代码中也有一些。假设 data[0]
在 INT_MAX == 32767
.
unsigned char
、data[0] << 24
is undefined behaviour