C Standard Library: , , & Error Handling Masterclass
Welcome to Phase 17 (Chapter 46): C Standard Library โ <assert.h>, <stddef.h>, <limits.h> & Error Handling Masterclass! This chapter covers the defensive programming infrastructure of C: runtime assertions that catch impossible states, compile-time assertions for type sizes, platform limit constants, and non-local jumps for error recovery without exceptions.
assert(expression) from <assert.h> evaluates an expression at runtime. If it evaluates to zero (false), the program prints an error message showing file, line number, and expression text, then calls abort().
When to use assert() vs if/return:
โข assert() โ For detecting programmer bugs (impossible states, violated preconditions). Should NEVER fire in production.
โข if/return error โ For handling runtime errors from external inputs (bad user data, I/O failures, NULL returns). Must be handled gracefully.
โข Define NDEBUG in release builds (gcc -DNDEBUG) to strip ALL assert() calls to zero overhead.
C11 Static Assertions: _Static_assert(sizeof(int) == 4, "int must be 32 bits"); โ evaluated at compile time, zero runtime cost. Stops compilation if violated.
| Identifier | Type / Value | Purpose |
|---|---|---|
NULL | Null pointer constant | Platform-correct null pointer (0 or (void*)0) |
size_t | Unsigned integer | Result of sizeof. Array indices. Byte counts. |
ptrdiff_t | Signed integer | Result of pointer subtraction. Can be negative. |
offsetof(type, member) | size_t macro | Byte offset of struct member from struct start. Used in binary protocols and container_of patterns. |
Every C platform has different widths for primitive types. <limits.h> exports the exact min/max constants for the current compilation target:
Key limits.h Constants:
โข CHAR_BIT = 8 (bits per byte, always 8 on modern hardware)
โข INT_MIN = -2,147,483,648 | INT_MAX = 2,147,483,647
โข LONG_MAX = 9,223,372,036,854,775,807 (on 64-bit Linux)
โข CHAR_MIN = -128 | CHAR_MAX = 127 (signed char)
โข UINT_MAX = 4,294,967,295 (unsigned int)
#include <stdio.h>
#include <assert.h>
#include <stddef.h>
#include <limits.h>
#include <stdint.h>
/* Compile-time assertion: struct must be exactly 16 bytes for binary protocol */
typedef struct {
uint8_t type; /* offset 0 */
uint8_t flags; /* offset 1 */
uint16_t length; /* offset 2 */
uint32_t id; /* offset 4 */
uint64_t payload; /* offset 8 */
} __attribute__((packed)) PacketHeader;
_Static_assert(sizeof(PacketHeader) == 16, "PacketHeader must be 16 bytes!");
static int divide(int a, int b) {
assert(b != 0 && "Divisor must not be zero!");
return a / b;
}
int main(void) {
printf("INT_MAX = %d\n", INT_MAX);
printf("LONG_MAX = %ld\n", LONG_MAX);
printf("offsetof(PacketHeader, id) = %zu\n", offsetof(PacketHeader, id));
printf("offsetof(PacketHeader, payload) = %zu\n", offsetof(PacketHeader, payload));
printf("divide(10, 2) = %d\n", divide(10, 2));
/* divide(10, 0) would trigger assert and abort */
return 0;
}Q1: Should assert() be used for NULL pointer checks?
Only for NULL pointers that represent programmer bugs (e.g. internal preconditions). For NULL from user input or library calls, use if (!ptr) { handle_error(); }.
Q2: What happens when _Static_assert fails?
The compiler emits an error with your custom message string and stops compilation. Zero runtime overhead โ it is purely a compile-time gate.
Q3: What is the container_of macro?
A common Linux kernel macro using offsetof() to recover a pointer to the containing struct from a pointer to one of its members โ foundational to linked list implementations.
Q4: When would ptrdiff_t be negative?
ptrdiff_t = ptr2 - ptr1 is negative when ptr2 points to an earlier memory address than ptr1. This occurs when iterating backward through an array.
Q5: What is setjmp/longjmp used for?
setjmp() saves the CPU register state; longjmp() restores it โ effectively a non-local goto. Used in error recovery and parser frameworks. Extremely error-prone; avoid in new code.