Expanding the macros, the relevant expression becomes
((p = handlers[0]) ? (*p)() : 0)|((p = handlers[1]) ? (*p)() : 0)
That contains the following sub-expressions involving p:
A: p = handlers[0]
B: (*p)()
C: p = handlers[1]
D: (*p)()
There's a sequence point associated with the first ?: expression
separating A and B(6.5.15p4), guaranteeing that A is sequenced before B
(5.1.2.3p3) . There's a sequence point associated with the second ?:
expression separating C and D, guaranteeing that C is sequenced before
D. There is no sequence point associated with the |, so the two
evaluations of DO_IT() are unsequenced relative to each other.
Importantly, this means that it is permissible for the evaluation of the
two ?: expressions to be interleaved (see footnote 13).
In itself, this would be bad enough, because the possibility of
interleaving those evaluations means that, in each of the two places
where (*p)() is evaluated, there's no guarantee whether p has the value
handlers[0] or handlers[1].
However, because A and B both assign values to the same variable, the
fact that they are not sequenced relative to each other has worse
implications: the behavior is undefined (6.5p2).
While I've worded this argument in terms of the new "sequencing"
terminology introduced in C2011, the same conclusion applied (though
less clearly) in older versions of the standard. The relevant clause was
one I have occasionally referred to as one of the most confusing in the
standard: "Between the previous and next sequence point an object shall
have its stored value modified at most once by the evaluation of an
expression. 72) Furthermore, the prior value shall be read only to
determine the value to be stored." (C99, 6.5p2).
That version of the standard didn't explicitly permit interleaved
evaluation, it simply failed to prohibit it. A defect report resolution
confirmed that failing to prohibit interleaving was deliberate.