负零的C标准(1的补码和有符号的幅度)

时间:2011-10-28 05:44:52

标签: c standards bit-shift zero negative-number

所有这些功能都可以在我的机器上提供预期的结果。他们都在其他平台上工作吗?

更具体地说,如果x在1的补码机器上具有位表示0xffffffff或在有符号的幅度机器上具有0x80000000,那么标准对于(无符号)x的表示有什么说明?

另外,我认为v2,v2a,v3,v4中的(unsigned)强制转换是多余的。这是对的吗?

假设sizeof(int)= 4且CHAR_BIT = 8

int logicalrightshift_v1 (int x, int n) {

    return (unsigned)x >> n;
}

int logicalrightshift_v2 (int x, int n) {

    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v2a (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v3 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x < 0 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v4 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (((unsigned)x & 0x80000000) >> n);
}

int logicalrightshift_v5 (int x, int n) {

    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

int logicalrightshift_v6 (int x, int n) {

    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}

2 个答案:

答案 0 :(得分:8)

  

如果x在1的位上具有位表示0xffffffff   补充机器或0x80000000在签名量级机器上   标准是否表示​​(无符号)x?

的表示

转换为unsigned是根据指定的,而非表示形式。如果您将-1转换为unsigned,则始终获取UINT_MAX(因此,如果您的unsigned为32位,则始终获得4294967295 })。无论您的实现使用的有符号数字的表示如何,都会发生这种情况。

同样,如果您将-0转换为unsigned,那么您始终获取0-0在数值上等于0.

请注意,不需要补码或符号幅度实现来支持负零;如果没有,则访问此类表示会导致程序具有未定义的行为。

逐个完成您的功能:

int logicalrightshift_v1(int x, int n)
{
    return (unsigned)x >> n;
}

x的负值此函数的结果取决于UINT_MAX,如果(unsigned)x >> n不在int范围内,则会进一步实现定义。例如,logicalrightshift_v1(-1, 1)将返回值UINT_MAX / 2,无论机器使用哪种代表签名数字。

int logicalrightshift_v2(int x, int n)
{
    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

几乎所有关于此的内容都可以是实现定义的。假设您试图在msb中创建一个值,符号位为1,值位为零,则不能通过使用移位来移植 - 您可以使用~INT_MAX,但这是允许在不允许负零的符号幅度机器上具有未定义的行为,并且允许在两个补码机器上给出实现定义的结果。

0x7fffffff0x80000000的类型取决于各种类型的范围,这将影响此表达式中其他值的提升方式。

int logicalrightshift_v2a(int x, int n)
{
    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

如果您创建的unsigned值不在int范围内(例如,给定32位int,值&gt; 0x7fffffff)则隐含return语句中的转换生成实现定义的值。这同样适用于v3和v4。

int logicalrightshift_v5(int x, int n)
{
    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

这仍然是实现定义,因为未指定int表示中的符号位是否对应于unsigned表示中的值位或填充位。如果它对应于填充位,则它可能是陷阱表示,在这种情况下,行为是未定义的。

int logicalrightshift_v6(int x, int n)
{
    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}

适用于v5的相同评论适用于此。

  

另外,我认为v2,v2a,v3,v4中的(unsigned)强制转换是多余的。是   这是正确的吗?

这取决于。作为十六进制常量,如果该值在0x80000000范围内,则int将具有类型int;如果该值在unsigned范围内,则为unsigned;如果该值在long范围内,则为long;否则unsigned long(因为该值在unsigned long)的最小允许范围内。

如果您希望确保它具有无符号类型,则使用U将常量后缀为0x80000000U


<强>要点:

  1. 将大于INT_MAX的数字转换为int会得到实现定义的结果(或者实际上,允许引发实现定义的信号)。

  2. 通过重复添加或减去unsigned来完成将超出范围的数字转换为UINT_MAX + 1,这意味着它取决于数学,不是代表。

  3. 检查否定的int表示为unsigned是不可移植的(正int表示也可以)。

  4. 通过使用按位运算符生成负零并尝试使用结果值是不可移植的。

  5. 如果您想要“逻辑转换”,那么您应该在任何地方使用无符号类型。签名类型用于处理算法,其中是重要的,而不是表示。

答案 1 :(得分:2)

如果您遵循该标准,那么在所有平台上都不能保证这些标准不相同。

在v5中,违反了严格别名,这是未定义的行为。

在v2 - v4中,您已经签署了右移,这是实现定义的。 (有关详细信息,请参阅评论)

在v1中,您已签名为无符号演员,这是在数字超出范围时定义的实现。

编辑:

v6 可能实际上有以下假设:

  • 'int'是2或1的补充。
  • unsignedint的大小完全相同(包括字节和位,并且密集包装)。
  • unsigned的结束符合int的结尾。
  • 填充和位布局是相同的:(有关详细信息,请参阅caf的评论。)