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