Skip to content

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:

AreaRepresentative 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; errno can 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.

Further reference ​