将两个无符号数减去一个有符号数的语义

The semantics of storing the subtraction of two unsigneds to a signed

我正在减去无符号数并将它们存储为有符号类型,并且它可以正常工作。我不完全明白为什么这会起作用。举个例子:

    #include <stdio.h>

    int main(void)
    {
        uint32_t a = 1;
        uint32_t b = 2;
        int32_t c = a - b;
        printf("%"PRId32"\n", c);
        return 0;
    }

这个减法的结果是-1,貌似只有-1,因为我的电脑是二进制补码。我对么?我正在查看 C11 规范。

如果我们剖析以下语句:

        int32_t c = a - b;

根据运算符优先级(如第 76 页注释 85 所述),我们从减法开始:

a - b

C11 6.5.6/6:

The result of the binary - operator is the difference resulting from the subtraction of the second operand from the first.

这是 -1,因此不适合。转换! C11 6.3.1.3/2:

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

所以a-b的实际值为4294967295。接下来,赋值运算符:C11 6.5.16.1/2:

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

因此需要将无符号值4294967295转换为有符号值。都有些什么样的规矩? C11 6.3.1.3/3:

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

所以这是完全定义的实现,因此最终为 -1,因为这就是我的实现方式。

真的是这样吗?如果我在具有补码系统的系统上 运行 代码会产生不同的值吗?还是我忽略了什么?

The result of this subtraction is -1, and it seems like it is only -1 because my computer is two's complement. Am I correct?

赋值左值转换前,a - b实际运算结果为4294967295 = 2^32 - 1 = UINT_MAX,因为减法是对uint32_t 操作数和环绕是明确定义的,根据您引用的 6.3.1.3/2 规则。无论您的系统使用哪种签名格式,您都将获得 4294967295

Is this really the case?

是的,您已经正确阅读并引用了标准。

Would the code result in a different value if I run it on a system with one's complement system?

是的。这是数字的原始二进制数是 0xFFFFFFFF,因此 impl.defined 转换为带符号很可能将其转换为该原始二进制文件的带符号格式的相应表示:

  • 在 2 的补码系统中,0xFFFFFFFF 给出 -1
  • 在 1 的补码系统上,它会给出 -0,这可能是陷阱表示。
  • 在带符号的星等系统上,它会给出 -2147483647

不允许使用其他形式(C17 6.2.6.2/2)。

Or am I overlooking something?

一个小细节。与 C 的整数转换规则不同,int32_t 类型特别没有废话。 保证 (7.20.1.1) 始终使用 2 的补码并且没有填充位。具有 1 的复杂性的奇异符号系统。或带符号的幅度将不容易支持 int32_t - 它实际上是一种可选类型,仅对于 2 的补码实现是强制性的。因此,如果不支持 2 的补码,您的代码将无法编译。

另请注意,严格来说,int32_t 的正确格式说明符是 "%"PRId32 而不是 %d.