问题描述
那么我可以将值转换为无符号值,执行操作并转换回来,并获得相同的结果吗?我想这样做是因为无符号整数可能溢出,而有符号整数则不能。
解决方法
无符号整数算法在 C 术语中不会溢出,因为它被定义为包含模 2N,其中 N 是所操作的无符号类型中的位数,根据 C 2018 6.2.5 9:
... 涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会以比结果类型可以表示的最大值大 1 的数为模减少。
>对于其他类型,如果发生溢出,C 标准未定义行为,根据 6.5 5:
如果在表达式求值过程中出现异常情况(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则行为未定义。请注意,不仅结果是未定义的;程序的整个行为是未定义的。它可能会给出您意想不到的结果,可能会陷入陷阱,或者可能会执行与您预期完全不同的代码。
关于您的问题:
那么我可以将值转换为无符号值,执行操作并转换回来,并获得相同的结果吗?
我们有两个问题。首先,考虑给定 a + b
的 int a,b;
。如果 a + b
溢出,则 C 标准未定义该行为。所以我们不能说转换为 unsigned
、添加和转换回 int
是否会产生相同的结果,因为 a + b
没有定义的结果开始。
其次,根据 C 6.3.1.3,转换回部分是实现定义的。考虑 int c = (unsigned) a + (unsigned) b;
,它将 unsigned
和隐式转换为 int
以存储在 c
中。第 1 段告诉我们,如果总和的值可以用 int
表示,那么它就是转换的结果。但是第 3 段告诉我们如果值在 int
中无法表示会发生什么:
否则,新类型是有符号的,值不能在其中表示;要么结果是实现定义的,要么引发实现定义的信号。
GCC,例如 defines the result to be the result of wrapping modulo 2N。因此,对于 int c = (unsigned) a + (unsigned) b;
,如果 int c = a + b;
包裹模 2N,GCC 将产生与 a + b
相同的结果。但是,GCC 不保证后者。优化时,GCC 预计不会发生溢出,这可能会导致它消除程序允许发生溢出的任何代码分支。 (GCC 可能有一些处理溢出的选项。)
此外,即使有符号算术和无符号算术都回绕,使用无符号值执行运算并转换回在数学上不会产生与使用有符号值执行运算相同的结果。例如,考虑-3/2
。 int
结果为 -1。但是如果将 -3
转换为 32 位无符号,结果值为 232−3,那么 (int) ((unsigned) -3 / (unsigned) 2)
为 2−31− 2 = 2,147,483,646。