26

另一个问题中,出现了话题std::numeric_limits<int>::is_modulo。但是我想得越多,规范或 GCC 或两者似乎都有问题。

让我从一些代码开始:

#include <limits>
#include <iostream>

bool test(int x)
{
    return x+1 > x;
}

int main(int argc, char *argv[])
{
    int big = std::numeric_limits<int>::max();

    std::cout << std::numeric_limits<int>::is_modulo << " ";
    std::cout << big+1 << " ";
    std::cout << test(big) << "\n";
}

当我使用g++ -O3 -std=c++11(x86_64 GCC 4.7.2) 编译它时,它会产生以下输出:

1 -2147483648 1

即,is_modulo为真,一加INT_MAX为负,一INT_MAX加大于INT_MAX

如果您是那种有实际机会回答这个问题的人,那么您已经知道这里发生了什么。C++ 规范说整数溢出是未定义的行为;允许编译器假设您不这样做;因此论点x+1不能是INT_MAX;因此编译器可以(并且将)编译该test函数以true无条件返回。到目前为止,一切都很好。

但是,C++11 规范也说(18.3.2.4 第 60-61 段):

static constexpr is_modulo;

如果类型为模,则为真。222 如果对于任何涉及+-*对该类型的值的结果超出范围的操作[min(),max()],返回的值与真值相差 的整数倍,则该类型为模max() - min() + 1

在大多数机器上,这false适用于浮点类型、true无符号整数和true有符号整数。

请注意,第 5 节第 (4) 节仍然写道,“如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。” 没有提到is_modulo == true创建异常。

所以在我看来,标准在逻辑上是矛盾的,因为整数溢出不能同时定义和未定义。或者至少,GCC 是不符合标准的,因为is_modulo即使true有符号算术肯定不会环绕它。

标准的越野车吗?GCC 不合格吗?我错过了什么吗?

4

1 回答 1

18

如果is_modulo对于通常算术转换未更改的true有符号类型(例如int),则对于除被零除以外的任何算术运算,在模映射到范围内的单个值的(数学)整数中都有一个正确的结果类型,因此实现被限制为好像实际结果是对类型范围取模的真实结果。因此,如果一个实现想要将溢出算术保留为未定义,它必须设置is_modulofalse.

这在 gcc 邮件列表中被讨论得令人作呕,然后在PR 22200下进行了讨论,最终得出的结论是 的值is_modulo应该是false有符号类型的;今年 4 月对 libstdc++ 进行了更改。

请注意,在 C++03 中,语言明显不同:

18.2.1.2 numeric_limits 成员 [lib.numeric.limits.members]

56 - [...] 如果可以将两个正数相加并且结果可以环绕到第三个较小的数字,则该类型是模数。

鉴于未定义的行为一切皆有可能,有争议的是 libstdc++ 的先前行为(具有is_moduloas true)是正确的并且与 g++ 的行为一致;在阅读之前关于链接 PR 的讨论时,应牢记这一点。

于 2012-11-07T17:32:33.793 回答