The standard does not define every program. It classifies the gaps so that compilers can target different machines and optimise well. Annex J of the standard lists the cases of each kind (C17 3.4, Annex J).
The terms
| Term | Meaning | Clause |
|---|---|---|
| Undefined behaviour | The standard imposes no requirements | 3.4.3, J.2 |
| Unspecified behaviour | Two or more possibilities are allowed, and the standard does not say which is chosen in each case | 3.4.4, J.1 |
| Implementation-defined behaviour | Unspecified behaviour where the implementation documents its choice | 3.4.1, J.3 |
| Locale-specific behaviour | Depends on local conventions of nationality, culture and language | 3.4.2, J.4 |
A compiler need not report undefined behaviour. It may assume the program never does it, and may use that assumption to optimise. So a program with undefined behaviour can behave strangely at points far from the error, and change with the compiler’s optimisation level.
Some requirements are constraints, whose violation needs a diagnostic (C17 5.1.1.3). Constraint violations are errors that the compiler must report, unlike undefined behaviour.
Common undefined behaviour
| Case | Example | Clause |
|---|---|---|
| Signed integer overflow | INT_MAX + 1 | 6.5 |
| Integer division by zero | x / 0, x % 0 | 6.5.5 |
| Dereferencing a null or invalid pointer | *p when p is null | 6.5.3.2 |
| Reading or writing outside an array | a[10] for int a[10] | 6.5.6 |
| Using an object after its lifetime | A pointer to a local that has gone out of scope | 6.2.4 |
Using memory after free | free(p); *p; | 7.22.3 |
| Freeing the same pointer twice | free(p); free(p); | 7.22.3.3 |
| Shifting by a negative or too large a count | 1 << 32 with 32-bit int | 6.5.7 |
| Reading an uninitialised automatic object | int x; return x; | 6.3.2.1 |
| Two unsequenced modifications | i = i++ + 1; | 6.5 |
| Modifying a string literal | char *s = "a"; s[0] = 'b'; | 6.4.5 |
Modifying a const object | Through a cast that removes const | 6.7.3 |
| Calling a function through an incompatible type | A cast between function pointer types | 6.5.2.2 |
| Violating strict aliasing | See below | 6.5 |
| Passing a null pointer to a function not allowing it | strlen(NULL) | 7.1.4 |
| Declaring a reserved identifier | int _Foo; | 7.1.3 |
Compilers can often detect these at run time with sanitisers, such as
-fsanitize=undefined and -fsanitize=address in GCC and Clang.
int check(int x)
{
if (x + 1 < x) /* signed overflow is undefined, so this may never be true */
return 0;
return 1;
}
x + 1 does not overflowUnspecified behaviour
- The order in which function arguments and operands are evaluated.
- Whether equal string literals share storage.
- The values of padding bytes when a value is stored in a structure or union.
- The order and contiguity of blocks returned by successive calls to
malloc,callocandrealloc.
A program is not incorrect because it relies on an unspecified choice being one of the allowed ones, but the result can differ between compilers, or between two evaluations.
Implementation-defined behaviour
- Whether
charis signed. - The sizes of the integer types, within the minimum ranges.
- The result of right-shifting a negative signed value.
- The result of converting an out-of-range value to a signed type.
- The behaviour of
intbit-fields, and the order of bit-fields. - The mapping between source bytes and characters.
- The search paths for
#include.
Each implementation must document these choices. C23 removed some of them by requiring two’s complement for signed integers.
Strict aliasing
An object’s stored value may be accessed only through an lvalue of a suitable type (C17 6.5). This is the effective type rule, usually called strict aliasing. An object may be accessed through a type that is:
- compatible with its effective type, or a qualified version of one;
- the signed or unsigned type corresponding to its effective type, or a qualified version of one;
- an aggregate or union type that has one of the above as a member;
- a character type.
A declared object’s effective type is its declared type. Allocated memory takes the effective type of whatever was last stored into it, other than through a character type.
Compilers rely on this rule. A write through a float * is assumed not to change
an int, so values may be cached across it.
float f = 1.0f;
unsigned u1 = *(unsigned *)&f; /* undefined: wrong type */
unsigned u2;
memcpy(&u2, &f, sizeof u2); /* well-defined, if the sizes match */
union { float f; unsigned u; } v;
v.f = 1.0f;
unsigned u3 = v.u; /* well-defined in C: the bytes are reinterpreted */
Character types are the exception that makes byte access possible: a char * or
unsigned char * may read any object. This is the correct way to inspect an
object’s representation. For reading a file format, use memcpy or
shifts on bytes rather than casting a buffer to a structure pointer, which also avoids
problems with alignment and byte order.