考虑以下代码:
static_assert(X > 0);
static_assert(Y > 0);
static_assert(((X * Y) / X) == Y);
WhereX
和Y
是模板参数,类型为size_t
.
此代码应该检查无符号整数的可能溢出(在编译时)。
我的问题是:是否可以保证编译器会首先诚实地计算乘积X * Y
,然后才诚实地除以X
?
由于X
和Y
是无符号的,在我看来,编译器必须考虑在可能溢出的情况下失去意义。因此,他必须先乘,再除。但我不确定。
编译器有权将表达式简化
X * Y / X == Y
为true
当且仅当确定这种简化不会违反程序的可观察行为。参见例如[intro.abstract] / 1:
此外,在[expr.pre] / 5中规定了不允许违反程序观察到的行为的优化:
现在让我们处理表达式中的“可观察行为”
X * Y / X == Y
。让表达式X
和Y
具有这些表达式的类型unsigned int
和值大于零。如果乘积
X * Y
不超过该值UINT_MAX
(即没有溢出),则等式X * Y / X == Y
为真。现在让产品
X * Y
大于UINT_MAX
。如果我们以实数进行计算,那么我们可以写出以下关系:但是,无符号整数的计算是根据模运算规则[basic.fundamental] / 2执行的:
这意味着当溢出时,乘积
X * Y
将不等于k * (UINT_MAX + 1) + r
,而只有r
。因此,表达式X * Y / X == Y
等价于表达式r / X == Y
,即为假(毕竟,当溢出k * (UINT_MAX + 1) / X
大于一时,我们只是将其丢弃!)X
因此,要检查两个非空表达式和Y
一个类型相乘时是否没有溢出,unsigned int
很有可能使用 checkX * Y / X == Y
。通常,编译器无权假设这种相等性总是正确的。在某些实现中,定义类型值的位数
unsigned short
是 16,定义类型值的位数int
是 32。那么,如果X
和Y
具有 typeunsigned short
,那么X * Y / X == Y
即使乘积X * Y
不能表示为,表达式也可以为真类型unsigned short
。在计算乘积之前,使用普通算术转换
X * Y
将二元运算符的算术操作数转换*
为通用类型。在这种特殊情况下,泛型类型是.int
让
X
和分别Y
相等。该值不能由 type 表示,但它完全适合 32 位 type ,并且是这种类型的操作数相乘。然后将该值除以,结果为,显然等于。65535
2
65535 * 2
unsigned short
int
65535 * 2
65535
2
Y
让
X
两者Y
相等65535
。值65535 * 65535
不能用 type 表示unsigned short
,但也不能用 32 位类型表示int
。尽管我们最初将无符号类型的值相乘,但仍会发生未定义的行为——通过正常算术转换将操作数强制转换为的有符号整数类型的溢出。类型
std::size_t
只是其他整数类型的别名。而且语言标准并没有具体说明是哪一种。语言标准允许存在在正常算术转换期间将类型操作数std::size_t
转换为有符号整数类型的实现(但是,我无法给出具有这种行为的编译器的单个真实示例)。因此,用表达式检查非溢出X * Y / X == Y
是有潜在危险的。PS
您可以使用以下表达式检查 product 是否存在溢出
X * Y
, whereX
和Y
typestd::size_t
:如果操作数可以变为零,那么: