6

以下代码在 clang++ 中工作,但在 g++ 中崩溃

#include<vector>
#include<iostream>

template<class Iterator>
double abs_sum(double current_sum, Iterator it, Iterator it_end){
    if (it == it_end)
        return current_sum;
    return abs_sum(current_sum+std::abs(*it),++it,it_end);
}


int main(int argc, char** argv){
    std::vector<double> values {1.0, 2.0,-5};

    std::cout << abs_sum(0.0,values.begin(),values.end()) << std::endl;;
}

罪魁祸首原来是这一行:

return abs_sum(current_sum+std::abs(*it),++it,it_end);

在 clang 中,*it在之前进行评估++it,在 g++ 中则相反,导致迭代器在被取消引用之前增加。事实证明,评估函数参数的顺序是实现定义的。

我的问题是:我如何捕捉到这种类型的错误?理想情况下,当我不小心依赖于实现特定的细节时,我希望有一个错误或至少一个警告。

即使使用 -Wall,clang 和 gcc 都不会产生任何警告。

4

3 回答 3

4

我的问题是:我如何捕捉到这种类型的错误?

你没有。未定义的行为是未定义的。你抓不住...

...但一些工具可以帮助您:

  • 您的编译器:启用所有警告(g++/clang++-Wall -Wextra -pedantic是一个好的开始);
  • cpp检查;
  • 铿锵分析器;

但他们不提供任何保证。这就是 C++的原因。您(您,编码员)最了解并且不要编写 UB。祝你好运。

于 2018-12-14T11:03:10.870 回答
3

不幸的是,即使使用-Wextra(记住,-Wall更像-Wsome,因此不足),也没有对此的警告,这有点令人失望。

在一个更简单的情况下,使用原语,其中 race* 对编译器来说更明显:

void foo(int, int) {}

int main()
{
    int x = 42;
    foo(++x, x);
}

......你警告:

main.cpp: In function 'int main()':
main.cpp:6:9: warning: operation on 'x' may be undefined [-Wsequence-point]
     foo(++x, x);
         ^~~
main.cpp:6:9: warning: operation on 'x' may be undefined [-Wsequence-point]

(*不是真正的比赛,但你知道我的意思)

但是编译器很难“知道”您对迭代器的操作分别是读取和写入。

最终,恐怕您将不得不依靠测试、智慧和进取心。:)

于 2018-12-14T11:26:22.697 回答
2

您最初拥有的不是未定义的行为,而是未指定的行为。编译器不需要为未指定的行为发出任何诊断。

几乎所有 C++ 运算符的操作数的求值顺序(包括函数调用表达式中函数参数的求值顺序和任何表达式中子表达式的求值顺序)是未指定的。编译器可以按任何顺序计算操作数,并且可以在再次计算同一表达式时选择另一个顺序。

但是在这种情况下,这种未指定行为的结果会导致结束迭代器的取消引用,这反过来又会导致未定义的行为。


GCC 和 Clang 没有任何通用编译器选项来为未指定的行为发出诊断。

在 GCC 中有fstrong-eval-order执行以下操作的选项:

按照 C++17 采用的从左到右的顺序计算成员访问、数组下标和移位表达式,并按照从右到左的顺序计算赋值。默认情况下使用 启用-std=c++17-fstrong-eval-order=some仅启用成员访问和移位表达式的排序,并且是默认不带-std=c++17.

还有一个选项-Wreorder(仅限 C++ 和 Objective-C++)执行此操作:

当代码中给出的成员初始化器的顺序与它们必须执行的顺序不匹配时发出警告

但我认为这些选项对您的特定情况没有帮助。

因此,在这种特殊情况下,您可以按预期顺序执行操作。

于 2018-12-14T11:03:33.060 回答