以下代码在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++中则相反,导致迭代器在被取消引用之前增加。事实证明,函数参数的求值顺序是由实现定义的。
我的问题是:如何捕捉这种类型的错误?理想情况下,当我意外地依赖于特定于实现的细节时,我希望有一个错误或至少一个警告。
clang和gcc都不会产生任何警告,即使使用了-Wall也是如此。
我的问题是:如何捕捉这种类型的错误?
你没有。未定义的行为是未定义的。你抓不到它…
但是一些工具可以帮助你:
- 您的编译器:所有警告都已启用(g++/crang++
-Wall -Wextra -pedantic
是一个良好的开端( - cppcheck
- clang分析仪
但他们不提供任何保证。这就是为什么C++很难。你必须(你,程序员(最清楚,不要写UB。祝你好运
不幸的是,即使使用-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]
(*不是真正的种族,但你知道我的意思(
但是编译器很难"知道"迭代器上的操作分别是读和写。
最终,恐怕你将不得不依靠考验、智慧和进取心
您最初拥有的不是未定义的行为,而是未指定的行为。编译器不需要来为未指定的行为发出任何诊断。
几乎所有C++运算符的操作数的求值顺序(包括函数调用表达式中函数参数的求值顺序和任何表达式中子表达式的求值顺序(未指定。编译器可以按任何顺序计算操作数,并可以在再次计算同一表达式时选择另一个顺序。
但在这种情况下,这种未指定行为的结果会导致端迭代器的取消引用,进而导致未定义的行为。
GCC和Clang没有任何通用编译器选项来发布未指定行为的诊断。
GCC中有选项fstrong-eval-order
,它执行以下操作:
按照从左到右的顺序评估成员访问、数组下标和移位表达式,并按照从右到左的顺序评估赋值,如C++17所采用的。默认情况下使用
-std=c++17
启用。-fstrong-eval-order=some
仅启用成员访问和移位表达式的排序,并且是不带-std=c++17
的默认值。
还有选项-Wreorder
(仅限C++和Objective-C++(可以做到这一点:
当代码中给定的成员初始化程序的顺序与它们必须执行的顺序不匹配时发出警告
但我认为这些选项对您的特定情况没有帮助。
因此,在这种特殊情况下,您可以按预期顺序执行操作。