如何安全地将 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、ab 中奇怪的一元提升规则,如果 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 documentationbyte是一个带符号的整数类型,尽管它有 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 chardata[0] << 24 is undefined behaviour