c++ - Undefined behavior when the offending expression is not used? -
in comment @marcvanleeuwen another q&a, suggested following undefined behavior (ub):
template<class fwdit, class t> fwdit find_before(fwdit before_first, fwdit last, t const& value) { return std::adjacent_find(before_first, last, [&](auto const& /* l */, auto const& r) { // note left argument of lambda not evaluated return r == value; }); } auto list = std::forward_list<int> { 1, 2 }; auto = find_before(list.before_begin(), list.end(), 1); // undefined behavior!? the ub comes fact binarypredicate supplied std::adjacent_find dereference 2 adjacent iterators @ time, first pair being list.before_begin() , list.begin(). since before_begin() not dereferencable, entail ub. on other hand, left argument never used inside lambda. 1 might argue under "as-if" rule unobservable whether derefence took place, optimizing compiler might elide altogether.
question: standard dereferencing undereferencable iterator it, when expression *it not used , optimized away? (or more generally, ub when offending expression not used?)
note: offending code easy remedy special casing first iterator (as done in updated original q&a).
before_begin() yields non-dereferenceable iterator; 1 applying unary * has undefined behavior, above code has undefined behavior:
1.3.24 [defns.undefined]
undefined behavior
behavior international standard imposes no requirements
now, true implementations of library, possible above code have defined behavior; example, in simple implementation of forward_list, operator* on iterator have effect of forming t& reference uninitialised area of aligned storage:
template<typename t> struct forward_list { struct node { node* next = nullptr; typename aligned_storage<sizeof(t), alignof(t)>::type buf; }; struct iterator { node* node; t& operator*() { return *static_cast<t*>(static_cast<void*>(node->buf)); } }; node head; iterator before_begin() { return {&head}; } }; in scenario *(list.before_begin()) has defined behavior, long result used in "limited ways" allowed 3.8p6.
however, small change scenario result in *(list.before_begin()) becoming undefined; example, optimising space implementation use node_base omits storage , derive concrete list nodes. or perhaps aligned storage contain object wrapping t, accessing wrapped t falls foul of 3.8p6. alternatively, debug implementation check dereferencing before-begin iterator , terminate program.
by as-if rule, implementation permitted eliminate evaluations have no visible side effects, doesn't result in code having defined behavior. if implementation debug-checks dereference, program termination side effect not eliminable.
more interestingly, if operation undefined behavior occurs on way forming t& reference, optimising compiler use infer code paths leading undefined behavior cannot occur. per reference implementation of adjacent_find, eliminating code paths leading offending *first give result find_before always return last.
Comments
Post a Comment