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

标签 c standards bit-shift zero negative-number

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

更具体地说,如果 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;
}

最佳答案

If x has the bit representation 0xffffffff on 1's complement machines or 0x80000000 on signed magnitude machines what does the standard says about the representation of (unsigned)x ?



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

同样,如果您转换 -0unsigned那么你总是得到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,值位为 0,则无法通过使用移位来便携地执行此操作 - 您可以使用 ~INT_MAX ,但是这允许在不允许负零的符号幅度机器上具有未定义的行为,并且允许在二进制补码机器上给出实现定义的结果。
0x7fffffff的种类和 0x80000000将取决于各种类型的范围,这将影响此表达式中其他值的提升方式。
int logicalrightshift_v2a(int x, int n)
{
    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

如果您创建一个 unsigned不在 int 范围内的值(例如,给定 32 位 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 的相同评论也适用于此。

Also, I think the (unsigned) cast in v2, v2a, v3, v4 is redundant. Is this correct?



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

如果你想确保它是无符号类型,那么在常量后面加上 U , 至 0x80000000U .

摘要:
  • 转换大于 INT_MAX 的数字至 int给出实现定义的结果(或者实际上,允许引发实现定义的信号)。
  • 将超出范围的数字转换为 unsigned通过重复加减 UINT_MAX + 1 来完成,这意味着它取决于数学值,而不是表示。
  • 检查阴性 int表示为 unsigned不可移植(不过,正 int 表示是可以的)。
  • 通过使用按位运算符生成负零并尝试使用结果值是不可移植的。

  • 如果您想要“逻辑移位”,那么您应该到处使用无符号类型。有符号类型是为处理算法而设计的,其中值是重要的,而不是表示。

    关于负零的 C 标准(1 的补码和有符号幅度),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7925598/

    相关文章:

    c - dbus_connection_send_with_reply 超时

    c - 链表 : count=count->next gives segmentation fault

    c - If语句和顺序执行

    c++ - 内存编辑功能

    c - 哪里可以在线阅读C99官方语言标准手册/引用/规范?

    c++ - 如果 volatile 是不必要的,为什么 std::atomic 方法提供 volatile 重载?

    c - C中的左移运算符

    algorithm - 二叉树 : Getting the path of an element from its signature

    c - 如何从数字中提取某些位并将它们放入新变量中?

    c - 在 C 中,常量移位与具有相同值的变量移位之间有区别吗?