Q: How can I avoid these undefined evaluation order difficulties if I don't feel like learning the complicated rules?
A: The easiest answer is that if you steer clear of expressions which don't have reasonably obvious interpretations, for the most part you'll steer clear of the undefined ones, too. (Of course, ``reasonably obvious'' means different things to different people. This answer works as long as you agree that a[i] = i++ and i = i++ are not ``reasonably obvious.'')
To be a bit more precise, here are some simpler rules which, though slightly more conservative than the ones in the Standard, will help to make sure that your code is ``reasonably obvious'' and equally understandable to both the compiler and your fellow programmers:
i = i + 1because although the object i appears twice and is modified, the appearance (on the right-hand side) which fetches i's old value is used to compute i's new value.
c = *p++is allowed under this rule, because the two objects modified (c and p) are distinct. The expression
*p++ = cis also allowed, because p and *p (i.e. p itself and what it points to) are both modified but are almost certainly distinct. Similarly, both
c = a[i++] and a[i++] = care allowed, because c, i, and a[i] are presumably all distinct. Finally, expressions like
*p++ = *q++and
a[i++] = b[j++]in which three things are modified (p, q, and *p in the first expression, and i, j, and a[i] in the second), are allowed if all three objects are distinct, i.e. only if two different pointers p and q or two different array indices i and j are used.
(c = getchar()) != EOF && c != '\n'(commonly seen in a while loop while reading a line) is legal because the second access of the variable c occurs after the sequence point implied by &&. (Without the sequence point, the expression would be illegal because the access of c while comparing it to '\n' on the right does not ``determine the value to be stored'' on the left.)