一个迂腐的程序员如何在不做任何标准不保证的假设的情况下防止这种情况发生?
一种方法是使用无符号整数。无符号整数的溢出行为是明确定义的,从有符号整数转换为无符号整数时的行为也是如此。
所以我认为以下内容应该是安全的(事实证明它在一些非常不起眼的系统上被严重破坏,请参阅帖子后面的改进版本)
uintmax_t j = i;
if (j > (uintmax_t)INTMAX_MAX) {
j = -j;
}
printf("Result: |%jd| = %ju\n", i, j);
那么这是如何工作的呢?
uintmax_t j = i;
这会将有符号整数转换为无符号整数。如果为正,则值保持不变,如果为负,则值增加 2 n(其中 n 是位数)。这会将其转换为一个大数(大于 INTMAX_MAX)
if (j > (uintmax_t)INTMAX_MAX) {
如果原始数字是正数(因此小于或等于 INTMAX_MAX),则什么也不做。如果原始数字为负,则运行 if 块的内部。
j = -j;
数被否定。否定的结果显然是负数,因此不能表示为无符号整数。所以它增加了 2 n。
所以在代数上,负 i 的结果看起来像
j = - (i + 2 n ) + 2 n = -i
聪明,但这个解决方案做了假设。如果 C 标准允许的 INTMAX_MAX == UINTMAX_MAX,则会失败。
嗯,让我们看看这个(我正在阅读https://busybox.net/~landley/c99-draft.html,这显然是标准化之前的最后一个 C99 草案,如果最终标准有任何变化,请告诉我。
当 typedef 名称仅在初始 u 不存在或存在时有所不同时,它们应表示相应的有符号和无符号类型,如 6.2.5 所述;一个实现不应该提供一个类型而不提供其对应的类型。
在 6.2.5 我看到
对于每个有符号整数类型,都有一个对应的(但不同的)无符号整数类型(用关键字 unsigned 指定),它使用相同的存储量(包括符号信息)并具有相同的对齐要求。
在 6.2.6.2 我看到
#1
对于 unsigned char 以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(后者不需要任何一个)。如果有 N 个值位,每个位应表示 1 和 2N-1 之间的 2 的不同幂,以便 > 该类型的对象应能够表示从 0 到 2N-1 的值 > 使用纯二进制表示;这应称为值表示。任何填充位的值都未指定。39)
#2
对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要任何填充位;应该只有一个符号位。作为值位的每个位应与相应无符号类型的对象表示中的相同位具有相同的值(如果有符号类型中有 M 个值位,无符号类型中有 N 个值位,则 M<=N)。如果符号位为零,则不应影响结果值。
所以是的,看起来你是对的,虽然有符号和无符号类型必须具有相同的大小,但对于无符号类型来说,它似乎确实比有符号类型多一个填充位是有效的。
好的,基于上面的分析揭示了我第一次尝试中的一个缺陷,我编写了一个更加偏执的变体。这与我的第一个版本相比有两个变化。
我使用 i < 0 而不是 j > (uintmax_t)INTMAX_MAX 来检查负数。这意味着即使 INTMAX_MAX == UINTMAX_MAX,算法也会对大于或等于 -INTMAX_MAX 的数字产生正确的结果。
我为 INTMAX_MAX == UINTMAX_MAX、INTMAX_MIN == -INTMAX_MAX -1 和 i == INTMAX_MIN 的错误情况添加了处理。这将导致我们可以轻松测试的 if 条件内的 j=0。
从C标准的要求可以看出,INTMAX_MIN不能小于-INTMAX_MAX -1,因为符号位只有一个,值位的个数必须等于或小于对应的无符号类型。根本没有剩下的位模式来表示较小的数字。
uintmax_t j = i;
if (i < 0) {
j = -j;
if (j == 0) {
printf("your platform sucks\n");
exit(1);
}
}
printf("Result: |%jd| = %ju\n", i, j);
@plugwash 我认为 2501 是正确的。例如,-UINTMAX_MAX 值变为 1:(-UINTMAX_MAX + (UINTMAX_MAX + 1)),并且不会被您的 if 捕获。– 海德 58 分钟前
嗯,
假设 INTMAX_MAX == UINTMAX_MAX 和 i = -INTMAX_MAX
uintmax_t j = i;
在此命令之后 j = -INTMAX_MAX + (UINTMAX_MAX + 1) = 1
如果 (i < 0) {
i 小于零,所以我们在 if 中运行命令
j = -j;
在此命令之后 j = -1 + (UINTMAX_MAX + 1) = UINTMAX_MAX
这是正确的答案,因此无需在错误情况下将其捕获。