如何解决为什么有时我会溢出,有时不会在 C 中
我有这个代码,两次计算给了我不同的结果。
第一个溢出,第二个只保留 2^31-1。
我不知道为什么。
int n4,n5;
n4 = pow(2,31);
n4 = n4 + n4;
n5 = pow(2,31) + pow(2,31);
printf("\nn4: %d,n5: %d",n4,n5);
/* n4: -2,n5: 2147483647 */
解决方法
pow
的返回值属于 double
类型。
因此,pow(2,31) + pow(2,31)
在 double
可以表示的范围内。
赋值 n5 = (double) ...
将 double
强制转换为 int
。但由于该值不能用 int
表示,因此结果为 (2^31)-1。基本上,您将 double
值限制为 int
((2^31)-1) 的最大值。
第一种情况是溢出,因为 n4 + n4
的结果是 int
类型。并且值超过最大值。
C 标准草案 N2176
6.3 转换
6.3.1.4 实数浮点数和整数
- 当浮点实型的有限值被转换为_Bool 以外的整数类型时,小数部分将被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为未定义。
是的,转换是 UB。但是为了解释 OP 的结果,我“假设”该值已被限制在 int
的范围内。
首先,“为什么有时我会溢出有时不会”是完全不正确的。整数溢出是一种错误情况,由整数算术的结果无法用表达式的类型表示。您似乎与它混淆的是 -2
,它是环绕的示例,整数溢出的未定义行为的可能表现之一。问题是你不能从结果中判断是否发生了整数溢出,因为整数溢出发生后程序的行为是未定义的,所以结果看起来好像没有发生。
但让我们仔细看看您的代码:
int n4,n5;
n4 = pow(2,31);
pow(2,31)
将产生一个值为 2
的 double,其值为 31
的幂 - 完全或近似,具体取决于实现的质量。确切值比 32 位 signed int
中可以存储的最大值多一。
通过截断小数 (C11/18 6.3.1.4p1) 将 double
转换为 int
- 当实数浮点型的有限值转换为
_Bool
以外的整数类型时,小数部分将被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为未定义
如果整数部分在目标类型中不可表示,则行为未定义。在这种情况下,如果 pow
实现不好,则该值可能略小于 2³¹,截断后的结果将等于 MAX_INT
(即 2³¹-1),并且不会发生未定义的行为。但如果它是好的,那么它的结果是 2³¹ 并且转换的行为是未定义的。
现在我们开始:
n4 = n4 + n4;
如果 pow(2,31);
转换为 int
导致未定义的行为,这已经是无稽之谈。如果不是(即它导致 2³¹-1,那么这将导致整数溢出,因此此处会发生未定义的行为。
n5 = pow(2,31)
的值将是类型为double
的 2³² 并且不能用 32 位signed int
表示,因此转换将导致未定义的行为。在这里,未定义的行为是未定义的:
printf("\nn4: %d,n5: %d",n4,n5); /* n4: -2,n5: 2147483647 */
现在您认为该结果究竟有多少未定义?好吧,它非常未定义:
如果你编译
gcc pow.c
然后运行,你得到
n4: -2,n5: 2147483647
如果你用
gcc -fno-builtin-pow pow.c -lm
编译,或者用31
问指数scanf
,你会得到n4: 0,n5: -2147483648
未定义行为未定义。
,n4:第一个赋值溢出,因为 MAX_INT 可能是 2^31-1。整数溢出是 6.3.1.3 未定义的行为:
否则,新类型是有符号的,值不能在其中表示;要么结果是实现定义的,要么引发实现定义的信号。
在下一行中,您添加之前获得的两个未定义值(即 -1)并获得 -2。
n5:
pow()
返回一个双精度值,所以右边的表达式是用双精度值完成的,然后转换为一个 int,这也是每个 6.3.1.4 的未定义行为:如果被转换的值在 可以表示但不能准确表示的值的范围,结果是 以实现定义的方式选择的最接近的较高或最接近较低的可表示值。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。