"int i = 1; Why (i >= 60 * 60 * 1000 / 1 * 1000)" 是真的吗?(涉及IT技术)

36

首先,定义两个无括号的常量表达式是我的错误:

#define BIG_INTERVAL 60 * 60 * 1000
#define SMALL_INTERVAL 1 * 1000

int i = 1;

if (i >= BIG_INTERVAL / SMALL_INTERVAL - 1)
{
    printf("Oops!\n");
}
在宏定义展开后的if语句是if(i >= 60 * 60 * 1000 / 1 * 1000 - 1)
这并不是我的意图。但是如果我写if (i >= 3600000000 - 1),则会发现一些奇怪的事情。它是错误的。 60 * 60 * 1000 / 1 * 1000 - 1的类型是什么?是int吗?

49
这就是明智的程序员不使用 #define 声明常量的原因。 - jalf
18
或者你可以记得用括号把它们包起来... - icktoofay
15
可以。就像如果你确保旁边有医生一样,开枪打自己的脚也可以接受?为什么不做正确的事情,使用真正的类型常量(比如static const int或者枚举)呢? - jalf
3
问题是括号可以解决这个特定问题,直到你忘记它们,即使你没忘记,你也可能会遇到另一个与宏相关的问题。当使用宏时,需要跟踪太多东西了。考虑我曾经工作过的一个项目中的这个宏:#define for_all( iterator_t, it, container ) for ( iterator_t it = (container).begin(); it != (container).end(); ++it ),用法为:for_all( std::vector<int>::const_iterator, it, v ) std::cout << *it; 简单吧? - David Rodríguez - dribeas
7
将问题重新标记为“c”。你的代码不是C++,在C++中,不应使用printf()和#define。 - Andreas Bonini
显示剩余14条评论
9个回答

69

int类型的所有运算结果都是int类型。因此,60 * 60 * 1000 / 1 * 1000 - 1的结果确实是int类型的。但预期的结果3599999999对于int来说太大了,所以该表达式的实际计算结果是-694967297(假设使用32位int和二进制补码表示)。

对于字面值3600000000,这种情况不会发生,因为大于INT_MAX的整型字面值属于一种能够支持完整值的类型。


21
“如果存在这样一种类型,它可以保存完整的值。” - 如果存在这样的类型。对于超过ULONG_MAX的文字量,不能保证能够保存完整的值。 - MSalters
4
整数(与堆栈相对)溢出 :) - Dark Star1
即使 60 * 60 * 1000 对于 int 来说可能太大了,可以看看我的答案。 - Keith Thompson

42

60 * 60 * 1000 / 1 * 1000 - 1 = 3600000 * 1000 - 1,这将导致int类型溢出,因此结果可能是任意值(在您的情况下为负数,但不一定是负数)。

要实现您想要的,请添加 ( ):

#define BIG_INTERVAL (60 * 60 * 1000)
#define SMALL_INTERVAL (1 * 1000)

15
赞成建议添加括号,因为它解释了真正的问题,但是避免使用宏将是更好的建议。然而,“溢出int类型,所以它是负数”使得这听起来好像前者总是暗示着后者。在有符号整数类型上,这是不确定行为,在实践中通常意味着你会得到可能是正数或负数的垃圾值......这里3600000 * 1000碰巧小于2^32但大于2^31,因此在具有32位“int”的常见平台上它是负数。 - Tony Delroy

12

这是我的测试结果:

60 * 60 * 1000 / 1 * 1000 will result to -694967296

(60 * 60 * 1000) / (1*1000) will result to 3600

你的操作存在问题,是计算优先级的问题。

你可能需要查看C++运算符优先级http://msdn.microsoft.com/en-us/library/126fe14k%28v=vs.80%29.aspx。你会发现结果为什么变成了-694967296,我认为这是溢出的影响。


@muntoo - 这里,我解释了它。 - kazinix

9
如果你使用的编译器中,int类型为64位,那么你会发现表达式的结果是错误的。如果你使用的编译器中,int类型为32位或16位,那么你的表达式行为是未定义的,因为有符号int类型的溢出不一定会绕回。可能你的表达式只是简单地绕回了,但也有可能不会。
3600000000是一个在编译时可见的常量,因此如果int类型仅为32位,则你的编译器将不得不选择long long(或只是long如果long是64位)。所以你的另一个表达式将通过足够的位数进行评估以避免溢出,并且结果是正确的。

我一直认为负数包装是标准的 - 它实际上是未定义的吗? - mafu
1
@mafutrct:C和C++标准在有符号类型的表示方面没有任何规定,所以不能保证您会环绕,因为2的补码不是强制性的(操作溢出也是未定义的)。但是对于n位有符号类型,标准规定模2^n算术。 - Alexandre C.
你知道哪个编译器使用64位的int类型? - Dov
1
@Alexandre C. 你可能是指“对于n位无符号类型,然而,…” - luiscubal
@AlexandreC.:我明白了,谢谢。所以我多年前写的C++程序又包含了另一个hack。 - mafu
@Alexandre:这并不完全正确。 C99指定有符号整数必须使用2的补码,1的补码或者符号-大小来表示。但是表示仍然不强制要求在有符号溢出时产生任何特定行为。 - Keith Thompson

4

可能是因为您的数据超出了int类型的范围,int类型的最大值为2147m或者更小,如果超过这个范围,它将变成负数。正如其他答案所指出的那样,当扩展时,除法不起作用,因此请在宏定义周围加上括号。


约为21.47亿,而非2.7百万(假设使用4字节整型)。 - Mitch Wheat
谢谢,我真的不应该在瞌睡的时候回答问题 :P - Jesus Ramos
1
值得注意的是,这可能发生在OP身上,因为内部表示使用二进制补码,但总体而言,溢出行为是未定义的。 - Maxpm
@Maxpm,你说得完全正确,因为有些编译器实际上通过将常量值分配给-1或其他一些值来处理溢出。 - Jesus Ramos

2

你很可能超出了有符号整数的有效值范围——3600000000是一个非常大的数字!

当这种情况发生时,该值将变为int数据类型中最小的负值。

这将导致你的语句成立。


1

该表达式的每个参数都是整数,因此结果将是一个整数。


是的,它们是整数。更准确地说,它们是 int 类型。(单词 integer 包括许多类型,从 charlong longint 是一个特定的类型,不仅仅是 integer 的缩写。) - Keith Thompson

1

我认为你对宏的工作原理感到困惑。你没有使用这些宏的值,而是使用了它们本身的方程式。我认为这就是你困惑的地方。我建议你在宏中加上括号或者不使用宏。


0

我没有看到任何人提到的一件事是,即使完全括号化宏定义也不能完全解决问题。

这个问题是:

#define BIG_INTERVAL 60 * 60 * 1000

(提问者承认缺少括号是一个问题)。但即使有:

#define BIG_INTERVAL (60 * 60 * 1000)

每个常量(60、60和1000)都可以表示为int,但乘积为3600000,而语言仅保证INT_MAX >= 32767

语言规定大整数常量的类型足够大以容纳它们的值(例如,100000可以是intlong int类型,具体取决于这些类型的范围),但对于表达式,即使是常量表达式,也没有这样的规定。

您可以像这样解决此问题:

#define BIG_INTERVAL (60L * 60L * 1000L)

但这会使它成为long类型,即使它不需要。

至于运算符优先级问题,这是我最喜欢的例子:

#include <stdio.h>

#define SIX 1+5
#define NINE 8+1

int main(void)
{
    printf("%d * %d = %d\n", SIX, NINE, SIX * NINE);
    return 0;
}

当然,输出结果是

6 * 9 = 42

(参见道格拉斯·亚当斯。)

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