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

tyt*_*yty 7 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;
}
Run Code Online (Sandbox Code Playgroud)

caf*_*caf 9

如果x在1的补码机器上具有位表示0xffffffff或在有符号量值的机器上具有0x80000000,那么标准对于(无符号)x的表示有何看法?

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

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

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

逐个完成您的功能:

int logicalrightshift_v1(int x, int n)
{
    return (unsigned)x >> n;
}
Run Code Online (Sandbox Code Playgroud)

负值的此函数的结果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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

int logicalrightshift_v5(int x, int n)
{
    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

这仍然是实现定义的,因为未指定表示中的符号位是否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;
}
Run Code Online (Sandbox Code Playgroud)

适用于v5的相同注释适用于此.

另外,我认为v2,v2a,v3,v4中的(unsigned)转换是多余的.它是否正确?

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

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


摘要:

  1. 转换的数大于INT_MAXint给出一个实现定义结果(或实际上,允许凸起实现定义的信号).

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

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

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

如果您想要"逻辑转换",那么您应该在任何地方使用无符号类型.签名类型旨在处理价值重要的算法,而不是表示.

  • 参考:6.2.6.2/2:"实现 - 定义...符号位为1且所有值位为零(对于前两个)是否为陷阱表示或正常值"."前两个"包括两个补码,这是三个中的第二个.因此,它是实现定义的行为未定义的情况之一:实现可以做任何事情,只要它记录了`~INT_MAX`是陷阱表示.但如果它记录了它是正常值,则定义行为.因此,严格遵守的程序不能使用它. (2认同)