符号扩展,JAVA 中的位移位。帮助理解 C 代码位

Sign extension, bit shifting in JAVA. Help understanding a C-code bit

我有以下 C 代码(来自 FFMPEG):

static inline av_const int sign_extend(int val, unsigned bits)
{
    unsigned shift = 8 * sizeof(int) - bits;
    union { unsigned u; int s; } v = { (unsigned) val << shift };
    return v.s >> shift;
}

我正在尝试在 JAVA 中重现此内容。但我很难理解这一点。不管我怎么折腾,我都离不开。

关于value参数:它取无符号字节值作为int。

位参数:4

如果值为255,位数为4,则returns-1。我无法在 JAVA 中重现此内容。很抱歉提出这样模糊的问题。但是你能帮我理解这段代码吗?

总的来说,我正在尝试在 JAVA 中对 EA ADPCM 音频进行编码。在 FFMPEG 中: https://gitorious.org/ffmpeg/ffmpeg/source/c60caa5769b89ab7dc4aa41a21f87d5ee177bd30:libavcodec/adpcm.c#L981

代码看起来如此奇怪的原因是 C 语言充满了在 Java 中定义明确的未定义行为。例如,在 C 中,将一个带符号的整数 left 移位,这样符号位的变化是 undefined behaviour,此时程序可以做 任何事情 - 无论编译器导致程序做什么 - 崩溃,打印 42,使 true = false,任何事情都可能发生,编译器仍然正确编译它。

现在代码使用 1 技巧将整数左移:它使用一个联合,将成员的字节放在彼此的顶部 - 使无符号和有符号整数占据相同的字节;位移位用 unsigned integer 定义;所以我们使用它进行无符号移位;然后使用带符号的移位向后移位(代码假定负数的右移产生正确的符号扩展负数,这也不是标准保证的,但通常这些类型的库有一个配置实用程序可以拒绝编译这样的一个非常深奥的平台;同样,该程序假设 CHAR_BIT 为 8;但是 C 仅保证 char 至少 8 位宽。

在Java中,您不需要像工会这样的东西来完成它;相反,你这样做:

static int signExtend(int val, int bits) {
    int shift = 32 - bits;  // fixed size
    int v = val << shift;
    return v >> shift;
}

在Java中,int的宽度总是32位; << 可用于有符号和无符号移位;并且没有扩展到符号位的未定义行为; >> 可用于带符号的移位(>>> 将是无符号的)。

严格来说,运行 此代码与此输入数据的结果具有未指定的结果,因为 C 中的带符号位移位仅在不满足此场景的情况下才正确定义。来自 C99 标准:

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has unsigned type or if E1 has signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and negative value, the resulting value is implementation-defined.

(强调我的)

但是假设您的实现定义了带符号的右移以扩展符号,这意味着如果设置了符号位,则左侧的 space 将填充 1,否则将填充为零; ffmpeg 代码显然期望是这种情况。发生了以下情况:shift 的值为 28(假设为 32 位整数)。二进制表示法:

00000000 00000000 00000000 11111111 = val
11110000 00000000 00000000 00000000 = (unsigned) val << shift

注意当解释(unsigned) val << shift为有符号整数时,随着代码的进行(假设two's complement表示,因为今天的计算机都使用1) ,该整数的符号位已设置,因此向右的带符号移位将用左侧的零填充,我们得到

11110000 00000000 00000000 00000000 = v.s
11111111 11111111 11111111 11111111 = v.s >> shift

...以二进制补码表示,即-1。

在 Java 中,这个技巧的工作方式相同——除了更好,因为那里的行为实际上是有保证的。只需:

public static int sign_extend(int val, int bits) {
  int shift = 32 - bits;  // int always has 32 bits in Java
  int s = val << shift;
  return s >> shift;
}

或者,如果您愿意:

public static int sign_extend(int val, int bits) {
  int shift = 32 - bits;
  return val << shift >> shift;
}

1 严格来说,由于历史原因,这种转换在 C 标准中也没有明确定义的值。曾经有使用不同表示的计算机,并且具有一组符号位的相同位模式在(例如)带符号的幅度表示中具有完全不同的含义。

given this code:

static inline av_const int sign_extend(int val, unsigned bits)
{
    unsigned shift = 8 * sizeof(int) - bits;
    union { unsigned u; int s; } v = { (unsigned) val << shift };
    return v.s >> shift;
}

'static' 修饰符表示该函数在当前文件外不可见。

'inline' 修饰符是编译器的 'request' 修饰符,用于将代码 'inline' 放置在调用函数的任何位置,而不是使用具有关联 call/return 的单独函数代码序列

'sign_extend'是函数名

 in C, a right shift, for a signed value will propagate the sign bit,
 In C, a right shift, for a unsigned value will zero fill.
 It looks like your java is doing the zero fill.

 regarding this line: 
 unsigned shift = 8 * sizeof(int) - bits;
 on a 32bit machine, an integer is 32 bits and size of int is 4
 so the variable 'shift' will contain (8*4)-bits

regarding this line:
union { unsigned u; int s; } v = { (unsigned) val << shift };
 left shift of unsigned will shift the bits left,
 with the upper bits being dropped into the bit bucket
 and the lower bits being zero filled.

regarding this line:
return v.s >> shift;
this shifts the bits back to their original position,
while propagating the (new) sign bit