A C implementation is the set of tools used to translate a C program, a corresponding Standard Library, and a corresponding run-time environment. The standards explicitly state what behaviour you should expect from a C program, but the behaviour does not have to be totally consistent from one platform to another, or between two implementations of C on the same platform.
Some behaviour is implementation-defined — the implementation is free to do what it wants, but the behaviour must be documented by the implementation, e.g., in its manual. You should avoid implementation-defined behaviour, except when you know or can detect that you are using a specific implementation.
Some behaviour is unspecified — the implemenation must choose one possible behaviour out of several defined by the standard. It doesn't have to be documented. This should be avoided in portable programs, unless you can portably detect which choice was made.
Everything that a standard doesn't define is undefined behaviour — the implementation can do whatever it wants, and without documentation. It may cause demons to come flying out of your nose. This should be avoided at all costs.
Note that one possible outcome of undefined behaviour is that the program continues running in an apparently correct fashion. ‘Undefined behaviour’ does not forbid the implementation to do something sensible if it can detect the programmer's mistake — it just isn't required to. A consequence of this is that testing some obscure programming practice in a small program, and demonstrating that it works on some system, is not evidence that that practice is portable to other systems.
List some surprising cases of undefined behaviour.