Never decrement a pointer to before the beginning of the object it points to. Memory addresses greater than an object's end are defined, memory addresses less than the beginning of an object are not defined, and may wrap around in the objects memory space.
For example:
token_type equation[N_TOKENS]; token_type *p2; p2 = &equation[N_TOKENS-1]; for (; p2 >= equation; p2--) { /* some code */ }
is bad code, because pointer "p2" will be decremented to before the beginning of "equation" and tested.
for (; p2 > equation; p2--) { /* some code */ }
is proper code, but will not process element zero of "equation", so we do this:
for (;; p2--) { /* do your stuff */ if (p2 == equation) break; }
Minimize side effects, gotos, and increment and decrement operators (++ and --). When you use ++ or --, try to put them in a statement by itself, do not put them in a conditional statement. The C compiler will make efficient use of them, no matter what you do, so this is the best way to make easier to read and maintain code.
Don't depend on a group of different variables having contiguous memory addresses. If you need this, put them all in a structure.
I have no complaints about the C programming language. It is flawless, except for the "break" statement and the standard C libraries. You cannot "break" out of a loop while inside a "switch" statement, but you can "continue" the loop.
Very efficient and portable computer programs are only possible with a language like C, in my opinion.