一个过于追求细节的程序员会如何防止这种情况发生,而不做任何标准未保证的假设呢?
一种方法是使用无符号整数。无符号整数的溢出行为是明确定义的,转换有符号整数到无符号整数的行为也是明确定义的。
因此,我认为以下代码应该是安全的(结果在某些非常晦涩的系统上是糟糕的,见后面的帖子中改进版)。
uintmax_t j = i;
if (j > (uintmax_t)INTMAX_MAX) {
j = -j;
}
printf("Result: |%jd| = %ju\n", i, j);
那么这是如何工作的?
uintmax_t j = i;
这将有符号整数转换为无符号整数。如果它是正数,值保持不变;如果它是负数,则值增加2n(其中n是位数)。这将把它转换为一个大数字(比INTMAX_MAX更大)。
if (j > (uintmax_t)INTMAX_MAX) {
如果原始数字为正(因此小于或等于INTMAX_MAX),则不执行任何操作。如果原始数字为负,则运行if块中的内容。
j = -j;
数字被否定了。否定的结果显然是负数,因此不能表示为无符号整数。因此,它增加了2
n。
因此,代数上看,负数i的结果如下:
j = - (i + 2
n) + 2
n = -i
“聪明,但这个解决方案做了一些假设。如果INTMAX_MAX == UINTMAX_MAX,这将失败,而这是C标准允许的。”
“嗯,让我们看看这个(我正在阅读
https://busybox.net/~landley/c99-draft.html,这显然是标准化之前的最后一个C99草案,如果在最终标准中有任何更改,请告诉我。”
“当定义仅在初始u的存在或不存在上不同的typedef名称时,它们应表示6.2.5中描述的相应的有符号和无符号类型;实现不应提供没有提供其相应类型的类型。”
“在6.2.5中,我看到:”
“对于每个有符号整数类型,都有一个相应的(但不同的)无符号整数类型(用关键字unsigned指定),它使用相同数量的存储空间(包括符号信息)并具有相同的对齐要求。”
“在6.2.6.2中,我看到:”
对于除了unsigned char之外的其他无符号整数类型,对象表示的位将被分成两组:值位和填充位(可以没有填充位)。如果有N个值位,则每个位将表示介于1和2N-1之间的不同的2的幂次方,使得该类型的对象能够使用纯二进制表示表示从0到2N-1的值;这将被称为值表示。任何填充位的值都是未指定的。 对于有符号整数类型,对象表示的位将被分成三组:值位、填充位和符号位。可能没有填充位;必须恰好有一个符号位。作为值位的每个位都应与相应的无符号类型的对象表示中的相同位具有相同的值(如果有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捕获。– hyde 58分钟前
嗯,
假设INTMAX_MAX == UINTMAX_MAX且i = -INTMAX_MAX
uintmax_t j = i;
执行此命令后,j = -INTMAX_MAX + (UINTMAX_MAX + 1) = 1
if (i < 0) {
i小于零,因此我们运行if内的命令
j = -j;
执行此命令后,j = -1 + (UINTMAX_MAX + 1) = UINTMAX_MAX
这是正确的答案,因此无需在错误情况下陷入陷阱。
%d
或其他整数或浮点scanf格式说明符。请使用strto
系列函数。而且,未定义的行为只有一种,那就是坏的行为。 - M.Mfscanf
规范。在C11中,它是7.21.6.2/10,“如果转换的结果不能被对象表示,则行为未定义”。因此,scanf
系列大多不适合用于生产。 - M.M