为什么这个使用移位的交换宏不能用于负数?

时间:2014-07-04 02:22:04

标签: c macros bit-shift

我在内置库中找到了一些我需要扩展的代码。

但它似乎被打破了:

#define BSWAP16(__x)    ((((__x) >> 8) | ((__x) << 8)))

与以下功能不同:

__builtin_bswap16()

这个程序证明了这一点。

#include <stdio.h>

#define BSWAP16(__x)    ((((__x) >> 8) | ((__x) << 8)))

int main(int argc, char* argv[])
{
    unsigned short a = (unsigned short)BSWAP16(0xff00);
    unsigned short b = __builtin_bswap16(0xff00);
    short c = (short)BSWAP16(-8);
    short d = __builtin_bswap16(-8);

    printf("a=%04x, b=%04x, c=%04x, d=%04x\n", a,b,c,d);
    return 0;
}

输出:

a=00ff, b=00ff, c=ffffffff, d=fffff8ff

我不想回答告诉我应该使用endian.h或__builtin_bswap16。在此平台/编译器配置上此内部库使用的目标平台上,我触发了默认代码,该代码默认使用上述宏。

所以我的问题是。为什么它不适用于负数?

如果我将-8表示为0xfff8的短值,则可以正常工作。

所以我猜测它与内部转换为int有关。

如何修复此宏才能正常工作?

2 个答案:

答案 0 :(得分:4)

左移一个负数的未定义行为,来自草案C99标准部分6.5.7 按位移位运算符表示(强调我的前进) :

  

E1的结果&lt;&lt; E2是E1左移E2位位置;腾空   位用零填充。如果E1具有无符号类型,则值为   结果是E1'2E2,比最大值减少了一个模数   在结果类型中可表示。 如果E1有签名类型和   非负值,E1'2E2可在结果类型中表示,   那就是结果价值; 否则,行为是   未定义。

因此-8左移的结果是不可预测的。转换为 unsigned short 可以解决问题:

BSWAP16((unsigned short)-8)

-8是一个整数常量( literal ),因为它没有后缀,所以 int int 可以承担它的价值。假设32-bit int 和二重补充将具有以下值:

 FFFFFFF8

转换为 unsigned short 将删除不需要的高位。转换为 unsigned int 将无济于事,因为它将保留更高的位。

同样正确地移动负数是实现定义的:

  

E1&gt;的结果&gt; E2是E1右移E2位位置。如果E1有   无符号类型或E1具有带符号类型和非负值,   结果的值是E1 /的商的不可分割的一部分   2E2。 如果E1具有带符号类型和负值,则为结果值   是实现定义的。

答案 1 :(得分:2)

你应该在移位之前屏蔽掉不需要的位。

#define BSWAP16(__x)    (((((__x) & 0xFF00) >> 8) | (((__x) & 0xFF) << 8)))

关于为什么它没有移动就无法工作,请注意-8确实是0xFFFFFFF8。没有屏蔽,有很多更高的1位。计算期间使用int