Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch undefined behaviour in function argument initialization

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.

like image 338
LKlevin Avatar asked Dec 14 '18 10:12

LKlevin


People also ask

How do you check for undefined behavior in C++?

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.

What is undefined behavior in C++?

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.

Why does C++ allow undefined behavior?

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).

Is there undefined behavior in Python?

Python has undefined behavior because it's implementations are written in languages that do.


3 Answers

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:

  • your compiler: all warnings enabled (g++/clang++ -Wall -Wextra -pedantic is a good start);
  • cppcheck;
  • clang-analizer;

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.

like image 103
YSC Avatar answered Sep 28 '22 11:09

YSC


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. :)

like image 35
Lightness Races in Orbit Avatar answered Sep 28 '22 11:09

Lightness Races in Orbit


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.

like image 41
P.W Avatar answered Sep 28 '22 12:09

P.W