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

时间:2015-03-25 20:23:35

标签: java c 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.它返回-1。我无法在JAVA中重现这一点。抱歉这样模糊的问题。但是你能帮我理解这段代码吗?

总体而言,我试图在JAVA中编码EA ADPCM音频。在FFMPEG中: https://gitorious.org/ffmpeg/ffmpeg/source/c60caa5769b89ab7dc4aa41a21f87d5ee177bd30:libavcodec/adpcm.c#L981

3 个答案:

答案 0 :(得分:3)

严格地说,使用此输入数据运行此代码的结果具有未指定的结果,因为C中的已签名位移仅在此方案不符合的情况下正确定义。从C99标准:

  

E1 >> E2的结果是E1右移E2位位置。如果E1具有无符号类型或E1具有签名类型和非负值,则结果的值是E1 / 2 {{1的商的整数部分}} 如果E2具有签名类型和负值,则结果值是实现定义的。

(强调我的)

但是,让我们假设您的实现定义了带符号的右移以扩展符号,这意味着如果符号位置位则左侧的空格将填充1,否则为零; ffmpeg代码清楚地预计会出现这种情况。发生以下情况:E1的值为shift(假设为32位整数)。用二进制表示法:

28

请注意,在将00000000 00000000 00000000 11111111 = val 11110000 00000000 00000000 00000000 = (unsigned) val << shift 解释为有符号整数时,代码将继续执行(假设two's complement表示,因为今天的计算机都使用 1 ),设置了该整数的符号位,因此右边的符号移位用左边的零填充,我们得到

(unsigned) val << shift

...在两个补码表示中,即-1。

在Java中,这个技巧以相同的方式工作 - 除了更好,因为实际上保证了行为。简单地:

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

或者,如果您愿意:

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;
}

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

答案 1 :(得分:0)

代码看起来很奇怪的原因是C语言充满了Java中定义明确的未定义行为。例如,在C位移位有符号整数 left ,以便符号位更改为未定义行为,此时程序可以执行任何 > - 无论编译器导致程序执行什么操作 - 崩溃,打印42,make true = false,任何事情都可能发生,编译器仍然可以正确编译它。

现在代码使用1个技巧来移动左边的整数:它使用一个联合,它将成员的字节放在彼此的顶部 - 使无符号和有符号整数占用相同的字节; bitshift用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位; <<可用于有符号和无符号转换;并且没有未定义的行为扩展到符号位; >>可用于签名班次(>>>将无签名)。

答案 2 :(得分:0)

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'修饰符是对编译器的“请求”,即在调用函数时将代码“内联”放置,而不是具有与关联的调用/返回代码序列分开的函数

'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