Indeterminate Values and Trap Representations
This chapter covers two low-level concepts that explain why reading uninitialized variables is dangerous: indeterminate values and trap representations.
Why This Matters
Reading an uninitialized variable is one of the most common C bugs. Its exact status — undefined behavior or unspecified value — depends on the type and whether the type has trap representations. Understanding this prevents "it worked in my test" bugs.
Prerequisites
c.object.1— object representation and effective type.
Core Concept
Indeterminate values
An object with automatic storage duration that is not initialized has an indeterminate value (for types without a guaranteed default). Reading an indeterminate value has special rules:
- For
unsigned char, reading an indeterminate value yields an unspecified
value (not UB).
- For most other types, reading an indeterminate value can be undefined
behavior, especially if the type has trap representations. In C17, reading an indeterminate value of a type that can hold a trap representation (e.g., some implementations for int) is UB; if no trap representation exists, it yields an unspecified value. VERIFIED
Trap representations
A trap representation is a bit pattern that does not represent a valid value of the type and, if used as a value, causes undefined behavior (often a hardware trap). Not all types have trap representations. On mainstream two's-complement machines, integer types have no trap representations, but floating-point types may, and padding bits (pre-C23) could create them.
Syntax
int x; /* indeterminate if automatic and uninitialized */
unsigned char c; /* indeterminate, but reading is unspecified (not UB) */
Examples
Uninitialized automatic int
#include <stdio.h>
int main(void)
{
int x; /* indeterminate */
printf("%d\n", x); /* may be UB or unspecified; never rely on it */
return 0;
}
This program's behavior is not portable. It may print garbage, may crash, or may be optimized unpredictably.
Uninitialized unsigned char
#include <stdio.h>
int main(void)
{
unsigned char c; /* indeterminate */
printf("%u\n", c); /* unspecified value, but not UB */
return 0;
}
Reading an unsigned char is always at least "unspecified value," never UB from the value itself.
Static storage is zero-initialized
#include <stdio.h>
int global; /* zero-initialized (not indeterminate) */
int main(void)
{
static int s; /* zero-initialized */
printf("%d %d\n", global, s); /* 0 0 */
return 0;
}
Objects with static storage duration are always initialized (to zero if no explicit initializer), so they are never indeterminate.
How It Works
Automatic objects are allocated on the stack and not automatically initialized. Their initial bytes are whatever happened to be in that memory. The compiler may track that a value is indeterminate and exploit that in optimization (for example, assuming a branch reading it is UB and removing it).
Variations
malloc'd memory
malloc returns storage with *indeterminate* contents. calloc zeroes the storage (all bytes zero), but be aware that all-bits-zero is not necessarily a valid value for all types on all implementations (for integers it is; for pointers it is a null pointer; for floating-point it is +0.0 in IEEE 754).
Padding bytes
Reading padding bytes of a struct (via unsigned char and memcpy) yields unspecified values; they are not guaranteed to be zero.
Common Mistakes
- Assuming uninitialized variables are zero.
- Assuming reading an uninitialized
intis "just garbage" (it may be UB). - Assuming
malloczeroes memory (it does not).
Undefined Behavior
- Reading an indeterminate value of a type with a trap representation can be
UB. VERIFIED
- Using an indeterminate pointer value (dereferencing it) is UB.
Portability
- The presence of trap representations is implementation-defined.
unsigned charhas no trap representations and reading an indeterminate
unsigned char is always unspecified (not UB). VERIFIED
Under the Hood
The compiler may use "indeterminate value" as a license to assume a read never happens legitimately and optimize accordingly (e.g., remove the read or assume a branch is unreachable). This is a real-world source of surprising behavior under optimization.
Practical Usage
- Always initialize automatic variables before use.
- Use
callocwhen you need zeroed allocation. - Inspect uninitialized bytes only via
unsigned charif you must, and treat
the result as unspecified.
Exercises
1. Write a program that reads an uninitialized int and run it with and without optimization; observe the difference. 2. Demonstrate that unsigned char uninitialized reads are at least not UB. 3. Show that static and global variables are zero-initialized. 4. Explain why calloc is safer than malloc for zero-initialized data.
Deep Challenge
Using the C standard's definitions, explain the precise difference between an "indeterminate value" and an "unspecified value," and why reading an indeterminate int can be UB while reading an indeterminate unsigned char is not. Cite the relevant rule category.
Related Concepts
c.lang.storage-duration— which objects are initialized.c.mem.malloc— allocated storage contents.c.ub.definedness— definedness classification.
References
- ISO/IEC 9899:2018 §3.19 (indeterminate value), §3.18 (unspecified value),
§6.2.6.1 (object representation), §6.7.9 (initialization).
Verification
unsigned charhas no trap representations.VERIFIED- Reading indeterminate non-
unsigned charcan be UB.VERIFIED - Static storage is zero-initialized.
VERIFIED mallocdoes not zero;callocdoes.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Indeterminate values
- [ ] Trap representations
- [ ] Uninitialized automatic variables
- [ ] unsigned char exception
- [ ] Static zero-initialization
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.obj.indeterminate | 0 | 6 |
| c.obj.trap | 0 | 6 |