在C语言中查找short int变量的最大值

5

我正在参与K&R的2-1题练习,目标是计算不同变量类型的范围。以下是我用来计算short int最大可包含值的函数:

short int max_short(void) {
    short int i = 1, j = 0, k = 0;
    while (i > k) {
        k = i;
        if (((short int)2 * i) > (short int)0)
            i *= 2;
        else {
            j = i;
            while (i + j <= (short int)0)
                j /= 2;
            i += j;
        }
    }
    return i;
}

我的问题是,这个函数返回的值是-32768,显然是错误的,因为我期望得到一个正数。我无法找出问题所在,我使用了相同的函数(更改了变量类型)来计算int可以包含的最大值,它有效了...
我认为问题可能是在ifwhile语句中的比较引起的,因此进行了类型转换,但这并没有帮助...
有什么想法是什么原因导致这个问题吗?提前感谢!
编辑:感谢Antti Haapala的解释,向符号位溢出会导致未定义行为,而不是负值。

短整型是一种有符号类型,如果值超过其正容量,则会溢出到负范围。最好的方法是查看“limits.h”。 - Saleem
2
@Saleem,语句“如果值超过,则溢出到负范围”是不正确的。正确的陈述是“行为未定义”。 - Antti Haapala -- Слава Україні
@AnttiHaapala,非常抱歉。您是正确的! - Saleem
3个回答

6
您不能使用这样的计算来推断有符号整数的范围,因为有符号整数溢出会导致未定义行为,而缩小转换最多只能得到一个实现定义的值或引发信号。正确的解决方案是只需使用<limits.h>中的SHRT_MAXINT_MAX等。通过算术推断有符号整数的最大值是C语言中的一个诡计问题,自1989年首个标准发布以来一直如此。
请注意,《K&R》原版比C语言标准化早11年,即使是第二版-“ANSI-C”版本也早于最终标准并且与之有所不同-它们是为一种与当今C语言几乎完全不同的语言编写的。
但对于无符号整数,您可以轻松地做到:
unsigned int i = -1;
// i now holds the maximum value of `unsigned int`.

哦,我的理解是,如果你不断地将“1”乘以二,那么“1”最终会被移动到符号位位置,使整个变量变为负数...所以没有办法计算出最大值吗?我认为这个练习的重点是找到一种计算方法来解决这个问题。 - ColdCoffeeMug
1
@Ryuuzaki_kun 不,将1移位到标志位位置具有未定义的行为,无论是通过 <<+ 还是 *... - Antti Haapala -- Слава Україні
@Ryuuzaki_kun 这种未定义行为允许进行许多优化,例如 https://kristerw.blogspot.fi/2016/02/how-undefined-signed-overflow-enables.html - 它是 C 编程语言中最大的陷阱之一。 - Antti Haapala -- Слава Україні
我不确定这是一个诡计问题。检查我的答案是否有一种方法。我想不出会失败的情况 - 它应该涵盖模糊的一补数和符号和幅度系统。 - Lundin
你可能想要补充说明,这种方法已经过时,因为它是不可移植的,自1990年以来,C标准将算术溢出定义为未定义行为。K&R比这更早15年以上,并且曾经是一种被接受的测试环绕的方法。 - chqrlie

0

根据定义,在C语言中,你不能使用同一类型的变量来计算该类型的最大值。这根本没有任何意义。当类型超出极限时,它将溢出。在有符号整数溢出的情况下,行为是未定义的,这意味着如果你尝试这样做,你将得到一个严重的错误。

正确的方法是简单地检查limits.h中的SHRT_MAX

另一种替代方案,有些更加可疑的方法是创建一个unsigned short的最大值,然后将其除以2。我们可以通过对值0进行按位反转来创建最大值。

#include <stdio.h>
#include <limits.h>

int main()
{ 
  printf("%hd\n", SHRT_MAX); // best way

  unsigned short ushort_max = ~0u;
  short short_max = ushort_max / 2;
  printf("%hd\n", short_max);

  return 0;
}

关于你的代码,有一个需要注意的地方:
((short int)2*i)>(short int)0 这样的强制类型转换是完全多余的。C语言中的大多数二元运算符,如 *> 都实现了一种叫做“通常算术转换”的方式,这是一种隐式转换和平衡表达式类型的方法。这些隐式转换规则将在不经意间使得两个操作数都成为 int 类型,尽管你进行了强制类型转换。

2
这种方法失败的边界情况是:有符号整数的最大值可能等于相应无符号整数的最大值。这样的实现非常罕见,但 C 语言允许这样做。我只遇到过最宽的无符号类型而不是 short 类型出现这种情况。 - chux - Reinstate Monica
2
小问题:unsigned short ushort_max = ~0u; 在目标类型为 unsigned 时可以,但在目标类型更宽的情况下不太好。some_unsigned_type u_max = -1; 更好。 - chux - Reinstate Monica
据我所知,C语言只允许三种有符号形式,列在6.2.6.2/2中:补码、反码和原码。其他任何形式都是不符合规范的。 - Lundin
至于第二条评论,这个答案明确是关于“带符号短整型”的。 - Lundin
2
考虑一个36位的二进制补码-无填充和一个35位的无符号数,其中1个填充。两者的最大值相同。 - chux - Reinstate Monica
1
@Lundin chux' 和我一样阅读标准。有符号类型的正数范围必须是无符号类型的子范围,但具有相同的范围也是可以接受的。 - Antti Haapala -- Слава Україні

-2

在比较时,您忘记了将其转换为短整型

好的,我假设计算机会通过变成负整数来处理整数溢出行为,因为我相信您在编写此程序时也是这样假设的。

输出32767的代码:

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
short int max_short(void)
{
    short int i = 1, j = 0, k = 0;
    while (i>k)
    {
        k = i;
        if (((short int)(2 * i))>(short int)0)
            i *= 2;
        else
        {
            j = i;
            while ((short int)(i + j) <= (short int)0)
                j /= 2;
            i += j;
        }
    }

    return i;
}

int main() {
    printf("%d", max_short());
    while (1);
}

添加了2个强制转换


2
另外,强制类型转换是纯粹的无意义操作。在强制转换之后,关系运算符的两个操作数仍然会执行通常的算术转换。这意味着最终仍然会转换为int类型。另外,请注意这不是C++标记。 - Lundin
我知道,但这就是海报试图做的事情,尽管它实际上并不有效,但它确实可以输出正确的值。 - leyanpan
3否则,新类型是有符号的,而该值无法在其中表示; 结果要么是实现定义的,要么就会引发实现定义的信号。 - Antti Haapala -- Слава Україні
(short int)(2 * i)中的强制类型转换有一定的意义,因为如果int乘积2*i没有溢出,则会将其转换为“实现定义或引发实现定义信号”的short。确实,结果随后被提升为int,但是产品的缩小意味着((short int)(2 * i))>(short int)0)不一定在功能上等同于(2 * i>0) - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接