Appearance
The C Standard Library and Operating-System Boundaries
The C standard library offers a portable foundation, not a complete modern application platform. Files, clocks, locale, signals, threads, and environment behavior sit near host boundaries where error handling and portability policies matter.
Headers group contracts
Common facilities include:
| Area | Representative headers |
|---|---|
| I/O and files | <stdio.h> |
| Allocation and conversion | <stdlib.h> |
| Strings and bytes | <string.h> |
| Integer properties | <stdint.h>, <inttypes.h>, <limits.h> |
| Time | <time.h> |
| Errors | <errno.h> |
| Assertions | <assert.h> |
| Locale and characters | <locale.h>, <ctype.h>, <wchar.h> |
| Threads and atomics since C11 | <threads.h>, <stdatomic.h> |
Including a header supplies declarations and macros. Some functionality also needs a compiled library, normally linked automatically for the core runtime. Historically, math facilities may require an explicit -lm on some Unix-like toolchains.
Streams abstract files and devices
FILE * represents a C stream. Open, check, use, and close it:
c
FILE *file = fopen(path, "rb");
if (file == NULL) {
/* errno commonly provides implementation-specific detail */
return 0;
}
/* fread/fwrite/fgets/fprintf with checked results */
if (fclose(file) != 0) {
/* a buffered write can fail during close */
}Text and binary modes differ notably on Windows. Portable binary formats should use binary mode. A successful write call may only update a buffer; flushing or closing can reveal later storage errors.
stdin, stdout, and stderr are standard streams. Command-line tools should keep machine-readable output on standard output and diagnostics on standard error.
errno is a protocol, not an exception object
Library and system functions document whether failure sets errno. Read it only after a function indicates failure; successful calls need not clear it. Preserve it before another library call if necessary. perror and strerror turn values into messages, with thread-safety and locale details depending on the chosen interfaces.
Many APIs use other error mechanisms: return codes, out parameters, Windows error state, or library-specific objects. Wrap them into a consistent domain boundary rather than assuming errno explains every failure.
Time has several meanings
Calendar time, elapsed monotonic duration, CPU time, and formatted local civil time are distinct. time_t and time represent calendar time in an implementation-defined encoding. clock measures processor time used by a program, not a reliable wall-clock stopwatch. C11 timespec_get provides a standardized time acquisition interface, while monotonic clocks commonly require POSIX or Windows APIs.
Local-time conversions may use shared internal storage in traditional APIs. Prefer documented reentrant platform variants or serialize access where portability wrappers require it.
Locale is process-wide state in traditional C
The initial C locale is predictable. Calling setlocale can change parsing, classification, and formatting behavior globally, which complicates libraries and threads. Machine-readable formats should specify locale-independent syntax; user-facing localization often benefits from a higher-level library.
Character classification functions such as isalpha require either EOF or a value representable as unsigned char. Passing a negative plain char value is undefined behavior:
c
if (isalpha((unsigned char)byte)) {
/* ... */
}Threads are standardized but not universally supplied
C11 introduced <threads.h> and <stdatomic.h>, but thread-library availability has historically varied. POSIX threads and Windows threading APIs remain common platform layers. Atomics are language/library facilities with a memory model; a mutex or thread handle still depends on implementation support.
Signals are a narrow asynchronous notification mechanism with severe restrictions on safely callable functions inside a handler. They are not ordinary callbacks. Keep handlers minimal and transfer work to normal control flow.
setjmp and longjmp provide nonlocal control transfer in <setjmp.h>. They can appear in older error-handling code and low-level runtimes, but jumping bypasses ordinary structured cleanup and leaves some modified automatic values indeterminate. Never jump into a function that has returned. New application code should prefer explicit error returns and centralized cleanup; C++ code must not use longjmp to cross scopes containing objects whose destructors need to run.
POSIX and Windows deserve adapters
Directory traversal, sockets, memory mapping, processes, terminal control, and many filesystem details are outside ISO C. POSIX and Windows provide different models. Isolate these APIs behind a small application interface so domain code stays testable and so platform differences remain explicit.
Optional prompts
Explain: Why should errno be inspected only after documented failure?
Answer: Successful functions may leave an old nonzero value in it. The function's return contract says whether an error occurred;
errnocan then provide detail when documented.
Debug: A binary file is corrupted only on Windows. Which seemingly harmless choice is worth checking first?
Answer: Whether it was opened in text rather than binary mode, allowing newline or end-of-file translation.
Explain: Why is longjmp especially dangerous across C++ frames?
Answer: It transfers control without C++ stack unwinding, so destructors for automatic objects are skipped and their resource invariants are broken.