The following code works in clang++, but crashes spectacularly in 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;;
}
The culprit turns out to be this line:
return abs_sum(current_sum+std::abs(*it),++it,it_end);
in clang, *it
is evaluated before ++it
, in g++ it's the reverse, causing the iterator to be increased before being dereferenced. It turns out that the order in which function arguments are evaluated is implementation defined.
My question is: How do I catch this type of error? Ideally I want to have an error or at least a warning when I'm accidentally depending on implementation specific details.
Neither clang nor gcc produces any warnings, even with -Wall.
However, it is possible to determine whether a specific execution of a C++ produced undefined behavior. One way to do this would be to make a C++ interpreter that steps through the code according to the definitions set out in the spec, at each point determining whether or not the code has undefined behavior.
So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.
Undefined behavior exists mainly to give the compiler freedom to optimize. One thing it allows the compiler to do, for example, is to operate under the assumption that certain things can't happen (without having to first prove that they can't happen, which would often be very difficult or impossible).
Python has undefined behavior because it's implementations are written in languages that do.
My question is: How do I catch this type of error?
You don't. Undefined behaviour is undefined. You cannot catch it...
... but some tools could help you:
-Wall -Wextra -pedantic
is a good start);They provide no guarantee though. This is why C++ is hard. You have (you, the coder) to know best and don't write UB. Good luck.
Unfortunately, even with -Wextra
(remember, -Wall
is more like -Wsome
and thus insufficient), there is no warning for this, which is a bit disappointing.
In a more trivial case, with a primitive, where the race* is more obvious to the compiler:
void foo(int, int) {}
int main()
{
int x = 42;
foo(++x, x);
}
… you are warned:
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]
(* not a real race but you know what I mean)
But it's just too tough for the compiler to "know" that your operations on the iterator are a read and a write respectively.
Ultimately I'm afraid you will have to rely on testing and wits and gumption. :)
What you have initially is not undefined behavior but unspecified behavior. The compiler is not required to issue any diagnostic for unspecified behavior.
Order of evaluation of the operands of almost all C++ operators (including the order of evaluation of function arguments in a function-call expression and the order of evaluation of the subexpressions within any expression) is unspecified. The compiler can evaluate operands in any order, and may choose another order when the same expression is evaluated again.
But the result of this unspecified behavior in this case leads to dereferencing of end iterator which in turn leads to undefined behavior.
GCC and Clang do not have any general compiler option to issue diagnostics for unspecified behavior.
In GCC there is the option fstrong-eval-order
which does the following:
Evaluate member access, array subscripting, and shift expressions in left-to-right order, and evaluate assignment in right-to-left order, as adopted for C++17. Enabled by default with
-std=c++17
.-fstrong-eval-order=some
enables just the ordering of member access and shift expressions, and is the default without-std=c++17
.
There is also the option -Wreorder
(C++ and Objective-C++ only) which does this:
Warn when the order of member initializers given in the code does not match the order in which they must be executed
But I do not think these options will be helpful in your particular case.
So in this particular case, you can perform operations in the intended order.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With