Undefined Behavior & Safety

undefined, unspecified, and implementation-defined behavior

People casually say a C program 'isn't guaranteed' to do something, but the standard is far more precise than that. It splits behavior the language does not nail down into THREE separate buckets, and confusing them is one of the most common mistakes a learner makes. Picture three doors. Behind the first, the rules are silent and anything may happen. Behind the second, there are a few allowed outcomes and you simply do not know which you will get. Behind the third, your specific compiler made a fixed choice and wrote it down.

Door one is undefined behavior: the standard imposes no requirements whatsoever, and the compiler may assume it never occurs (dereferencing a null pointer, signed overflow). Door two is unspecified behavior: the standard offers two or more permitted possibilities but does not require the implementation to document which it picks, and it may even differ from one occurrence to the next. A classic example is the order in which a function's arguments are evaluated — in f(g(), h()) the standard does not say whether g() or h() runs first, but both DO run and the program stays well-defined. Door three is implementation-defined behavior: like unspecified, there is a choice among allowed possibilities, but the implementation MUST document its choice and stick to it. Whether plain char is signed or unsigned, and how many bits an int has, are implementation-defined — your compiler's manual tells you the answer.

Why this matters: the difference is the difference between a portability concern and a catastrophe. Code that relies on unspecified or implementation-defined behavior is non-portable — it may behave differently elsewhere, but it still has SOME defined behavior on each platform, and you can write portable code by not depending on the choice. Code with undefined behavior is broken everywhere, full stop, because there is no behavior to rely on at all. So when you read 'the result is implementation-defined' you should think 'I should not assume a particular value'; when you read 'undefined' you should think 'I must not let this happen, ever'.

/* unspecified: which of a() or b() runs first is not fixed */ int r = a() + b(); /* implementation-defined: is plain char signed? compiler decides & documents */ char c = 200; /* value depends on your compiler's documented choice */

Both lines are non-portable but well-defined. Neither is undefined behavior — they still have some defined result on each platform.

Keep all three apart. Implementation-defined: documented choice. Unspecified: a permitted choice but undocumented and possibly varying. Undefined: no requirements at all, anything may happen. Only the third is a bug to be eliminated rather than merely a portability note.

Also called
the three kinds of non-portable behavior三種行為類別