7

所有这些功能都在我的机器上给出了预期的结果。他们都在其他平台上工作吗?

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

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

假设 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;
}
4

2 回答 2

10

如果 x 在 1 的补码机器上具有位表示 0xffffffff 或在有符号幅度机器上具有 0x80000000 ,那么标准对 (unsigned)x 的表示有何规定?

转换是根据unsigned指定的,而不是表示形式。如果你转换为,你总是得到(所以如果你是 32 位,你总是得到)。无论您的实现使用的带符号数字的表示如何,都会发生这种情况。-1unsignedUINT_MAXunsigned4294967295

同样,如果你转换-0unsignedthen 你总是得到0. -0在数值上等于 0。

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

逐一检查您的功能:

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

此函数对 的负值的结果x将取决于,如果不在 的范围内UINT_MAX,则将进一步由实现定义。例如,无论机器对有符号数字使用什么表示形式,都将返回该值。(unsigned)x >> nintlogicalrightshift_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在符号位中创建一个值,在值位中创建一个零值,您不能通过使用移位来便携地执行此操作 - 您可以使用~INT_MAX,但这允许在符号大小上具有未定义的行为不允许负零的机器,并且允许在二进制补码机器上给出实现定义的结果。

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

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

如果您创建的unsigned值不在int(例如,给定 32bit int, values > 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 中的(无符号)强制转换是多余的。这个对吗?

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

如果您希望确保它具有无符号类型,请在常量后面加上U, 到0x80000000U


概括:

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

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

  3. 检查负面int表示unsigned是不可移植的(int虽然正面表示是可以的)。

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

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

于 2011-10-28T06:38:13.637 回答
2

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

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

在 v2 - v4 中,您已经签署了右移,这是实现定义的。(更多细节见评论)

在 v1 中,您已签署无符号强制转换,这是在数字超出范围时定义的实现。

编辑:

考虑到以下假设,v6可能实际工作:

  • 'int' 是 2 或 1 的补码。
  • unsigned并且int大小完全相同(以字节和位为单位,并且密集打包)。
  • 的字节序unsigned匹配int.
  • 填充和位布局是相同的:(有关更多详细信息,请参阅 caf 的评论。)
于 2011-10-28T05:47:33.683 回答