Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coverage Ledger — C: Language Semantics, Standard Library, Process APIs, and Subprocesses (G4 schema)

Chapter: ./c.md — Section G, process runtime. This ledger inventories every documented API and behavior claim in the chapter as one row each, ordered by chapter section (§1 syntax/ semantics behavior rows first, then §2–§6 API/behavior rows in order of first appearance).

Scope. ISO C17 (draft N2310) baseline with C23 deltas verified against draft N3220 (open-std.org; C99 deltas vs N1256); POSIX.1-2024 / Issue 8 (pubs.opengroup.org); Linux man-pages 6.19 (man7.org); glibc 2.44, current stable verified via sourceware.org/glibc. The GNU C Library manual (gnu.org) was unreachable from the retrieval environment, so every glibc-behavior row cites the man page that documents glibc behavior instead (rows naming glibc scope). Linux kernel UAPI: git.kernel.org was bot-protected and raw.githubusercontent rate-limited at retrieval time, so kernel-side wait-status claims are anchored in man-pages 6.19; the affected row (PL-C-067, include/uapi/linux/wait.h) is the ledger’s single source-limited entry.

Status definitions.

  • documented — the claim is documented in ./c.md and anchored to a fetched authoritative source (ISO draft clause, POSIX page, or man page) exactly as the chapter cites it. No further work remaining.
  • source-limited — the claim is documented in ./c.md, but the intended primary source (the raw Linux UAPI header) was unreachable at retrieval time; anchored to the closest fetched authority (man-pages 6.19) instead.

Rollup: Inventoried: 82 / Documented: 81 / Remaining: 0 / Source-limited: 1

Section layout: §1 → PL-C-001–013 · §2 → PL-C-014–039 · §3 → PL-C-040–048 · §4 → PL-C-049–057 · §5 → PL-C-058–076 · §6 → PL-C-077–082.

IDLanguage/API or behaviorCanonical signatureDocumentation pathAuthoritative referenceVersion/platform scopeStatus
PL-C-001Translation-unit linkage: file-scope identifiers external by default, static internal, block scope none; extern in two TUs is one object; tentative definitions merged—./c.mdISO C17 draft N2310 §6.2.2, open-std.orgISO C17 (N2310)documented
PL-C-002Storage duration — static: lives for whole program execution, initialized before program startup—./c.mdISO C17 draft N2310 §6.2.4, §5.1.2, open-std.orgISO C17 (N2310)documented
PL-C-003Storage duration — automatic: destroyed at block exit; main’s automatic objects are destroyed before exit runs when main returns (not when main calls exit)—./c.mdISO C17 draft N2310 §6.2.4, §5.1.2.2.3 (fn.), open-std.orgISO C17 (N2310)documented
PL-C-004Storage duration — allocated: malloc/free, ownership manual and not tied to scope; the OS reclaims all allocated storage at termination—./c.mdISO C17 draft N2310 §7.22.3, open-std.orgISO C17 (N2310)documented
PL-C-005Storage duration — thread: _Thread_local, lifetime of the declaring thread—./c.mdISO C17 draft N2310 §6.2.4, open-std.orgISO C11+documented
PL-C-006Hosted entry point: main must return int (C23 footnote blesses char **argv equivalence)int main(void); / int main(int argc, char *argv[]);./c.mdISO C17 draft N2310 §5.1.2.2.1; C23 draft N3220 §5.1.2.3.2, open-std.orgISO C17/C23 (N2310/N3220)documented
PL-C-007main parameter rules: argc nonnegative, argv[argc] NULL, strings modifiable and retain last-stored values, argv[0] program name; POSIX: argc/argv/environ carry execve(2) arrays, environ initialized from envp—./c.mdISO C17 draft N2310 §5.1.2.2.1; POSIX.1-2024 exec, pubs.opengroup.orgISO C17 + POSIX.1-2024documented
PL-C-008Unsequenced modifications of one object (the i = i++ family) and data races between threads are undefined behavior—./c.mdISO C17 draft N2310 §5.1.2.4; Annex J.2, open-std.orgISO C11+documented
PL-C-009Signal-handler UB set: referring to non-atomic static/thread storage (beyond volatile sig_atomic_t assignment); calling library functions other than abort/_Exit/quick_exit/lock-free atomics/signal(sig,…); returning from SIGFPE/SIGILL/SIGSEGV handlers; signal() in a multithreaded program—./c.mdISO C17 draft N2310 §7.14.1.1¶3/¶5 (Annex J.2); C23 draft N3220 §7.14.1.1¶5/¶8, open-std.orgISO C17/C23documented
PL-C-010Termination UB set: exit called more than once or mixed with quick_exit; quick_exit twice or mixed with exit; signal raised while quick_exit executes; longjmp out of an atexit/at_quick_exit handler—./c.mdISO C17 draft N2310 §7.22.4.4¶2–3, §7.22.4.7¶2, open-std.orgISO C17 (N2310)documented
PL-C-011Unspecified/implementation-defined set: whether atexit concurrent with exit succeeds (unspecified); streams flushed/closed on _Exit/abort; which signals are SIG_IGN at startup; handler re-install/mask behavior after signal()—./c.mdISO C17 draft N2310 §7.22.4.2, §7.22.4.5/§7.22.4.1, §7.14.1.1¶3/¶6, open-std.orgISO C17 (N2310)documented
PL-C-012Normal vs abnormal termination vocabulary: exit/_Exit/quick_exit/return from main are normal; abort (via raise(SIGABRT)) is abnormal; POSIX classifies for the parent via WIFEXITED/WIFSIGNALED/WIFSTOPPED—./c.mdISO C17 draft N2310 §7.22.4, open-std.org; POSIX.1-2024 wait, pubs.opengroup.orgISO C17 + POSIX.1-2024documented
PL-C-013Version deltas: C99 adds _Exit (atexit minimum 32); C11 adds quick_exit/at_quick_exit/_Noreturn, allows handlers to touch lock-free atomics, <threads.h> optional via __STDC_NO_THREADS__; C17 no feature changes; C23 [[noreturn]] replaces _Noreturn, library renumbered (stdlib 7.22→7.24, exit §7.24.4.4), handler rules extended to <stdatomic.h>/constexpr; ≥32 atexit and ≥32 at_quick_exit registrations, lists distinct—./c.mdC99 draft N1256 §5.1.2.2.3, §7.20.4.4; C17 draft N2310 §7.22.4.2–§7.22.4.7, §7.14.1.1, §6.10.9; C23 draft N3220 §5.1.2.3.4, §7.24.4.4, §7.14.1.1¶5, §7.28, open-std.orgISO C99–C23 (N1256/N2310/N3220)documented
PL-C-014exit — normal termination: atexit handlers in reverse registration order, all open streams flushed and closed, tmpfile files removed, control to host; at_quick_exit handlers not called[[noreturn]] void exit(int status);./c.mdISO C23 draft N3220 §7.24.4.4 (C17: N2310 §7.22.4.4), open-std.orgISO C23 (C89+)documented
PL-C-015_Exit — normal termination classification; no atexit/at_quick_exit/signal handlers; flush/close implementation-defined[[noreturn]] void _Exit(int status);./c.mdISO C23 draft N3220 §7.24.4.5 (C17: N2310 §7.22.4.5), open-std.orgISO C23 (C99+)documented
PL-C-016quick_exit — runs only at_quick_exit handlers (reverse order), then _Exit(status); no atexit handlers, no signal handlers; UB if a signal is raised during its execution[[noreturn]] void quick_exit(int status);./c.mdISO C23 draft N3220 §7.24.4.7 (C17: N2310 §7.22.4.7), open-std.orgISO C23 (C11+)documented
PL-C-017abort — abnormal termination: raises SIGABRT; if caught and the handler returns, termination proceeds; flush/close implementation-defined; unsuccessful status[[noreturn]] void abort(void);./c.mdISO C23 draft N3220 §7.24.4.1 (C17: N2310 §7.22.4.1), open-std.orgISO C23 (C89+)documented
PL-C-018atexit — register normal-termination handler; ≥32 registrations; 0 on success, nonzero on failure; call concurrent with exit unspecified to succeedint atexit(void (*func)(void));./c.mdISO C23 draft N3220 §7.24.4.2 (C17: N2310 §7.22.4.2), open-std.orgISO C23 (C89+)documented
PL-C-019at_quick_exit — same contract as atexit for the quick_exit list; ≥32 entriesint at_quick_exit(void (*func)(void));./c.mdISO C23 draft N3220 §7.24.4.3 (C17: N2310 §7.22.4.3), open-std.orgISO C23 (C11+)documented
PL-C-020system (ISO C) — NULL: nonzero iff a command processor exists; non-NULL: pass string to processor, return value implementation-definedint system(const char *string);./c.mdISO C23 draft N3220 §7.24.4.8 (C17: N2310 §7.22.4.8), open-std.orgISO C23 (C89+)documented
PL-C-021getenv — pointer to associated string, shall not be modified by the program; not required to avoid data races with env-mutating threads; POSIX RATIONALE: ISO-C internal-buffer allowance no longer permitted — return value points into the environmentchar *getenv(const char *name);./c.mdISO C23 draft N3220 §7.24.4.6, open-std.org; POSIX.1-2024 getenv, pubs.opengroup.orgISO C23 + POSIX.1-2024documented
PL-C-022assert — on failure writes diagnostics to stderr and calls abort; compile-out when NDEBUG defined at the last <assert.h> inclusion; macro redefined per inclusionvoid assert(scalar expression);./c.mdISO C23 draft N3220 §7.2.1.1 (C17: N2310 §7.2.1.1), open-std.orgISO C23 (C89+)documented
PL-C-023signal — install SIG_DFL/SIG_IGN/handler; post-handler reinstall-vs-block implementation-defined; ISO C: multithreaded use is UB; POSIX requires thread-safety (RATIONALE) but sigaction is the recommended interface; returns previous handler or SIG_ERR (+ errno)void (*signal(int sig, void (*func)(int)))(int);./c.mdISO C17 draft N2310 §7.14.1.1, open-std.org; POSIX.1-2024 signal, pubs.opengroup.orgISO C17 + POSIX.1-2024documented
PL-C-024raise — send to the executing program/thread; POSIX: to the thread (effectively pthread_kill(pthread_self(), sig)); glibc implements via pthread_kill/tgkill; handler runs synchronously, 0 on successint raise(int sig);./c.mdISO C17 draft N2310 §7.14.2.1, open-std.org; POSIX.1-2024 raise, pubs.opengroup.org; man-pages 6.19 raise(3), man7.orgISO C17 + POSIX.1-2024; glibc/Linuxdocumented
PL-C-025sigaction — examine/modify per-signal action; act == NULL queries; the preferred interface over signal()int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);./c.mdPOSIX.1-2024 sigaction, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-026sigprocmask — change calling thread’s mask; behavior unspecified in a multithreaded processint sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);./c.mdPOSIX.1-2024 sigprocmask, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-027pthread_sigmask — per-thread mask; returns the error number directly (not -1/errno); how ∈ {SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK}int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);./c.mdPOSIX.1-2024 pthread_sigmask, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-028sigpending — pending-and-blocked set for the calling threadint sigpending(sigset_t *set);./c.mdPOSIX.1-2024 sigpending, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-029sigsuspend — atomically replace mask and wait for an unblocked signal; always returns -1 with EINTR when it returns; the race-free wait primitiveint sigsuspend(const sigset_t *sigmask);./c.mdPOSIX.1-2024 sigsuspend, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-030sigwait — synchronously consume one pending signal from set (which must be blocked); returns error number, never EINTR; no handler runsint sigwait(const sigset_t *restrict set, int *restrict sig);./c.mdPOSIX.1-2024 sigwait, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-031pause — suspend until a signal whose action is to run a handler or terminate; returns -1/EINTR only after a caught handler returnsint pause(void);./c.mdPOSIX.1-2024 pause, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-032alarm — schedule SIGALRM after realtime seconds; 0 cancels a pending alarm; requests not stacked; returns seconds remaining of a previous requestunsigned alarm(unsigned seconds);./c.mdPOSIX.1-2024 alarm, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-033kill — pid > 0: that process; 0: caller’s process group; -1: all permitted processes; < -1: process group |pid|; sig == 0 is the null signal (permission check only)int kill(pid_t pid, int sig);./c.mdPOSIX.1-2024 kill, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-034environ — NULL-terminated array of "name=value" strings; created by execve(2)’s envp; forked children inherit a copyextern char **environ;./c.mdPOSIX.1-2024 environ, pubs.opengroup.org; man-pages 6.19 environ(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-035setenv — copies the string; fails with EINVAL if name contains =; existing name + overwrite == 0 → success, no change; may ENOMEMint setenv(const char *envname, const char *envval, int overwrite);./c.mdPOSIX.1-2024 setenv, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-036unsetenv — removes variable; name with = or empty is EINVAL; absence is success; shall update the list of pointers to which environ pointsint unsetenv(const char *name);./c.mdPOSIX.1-2024 unsetenv, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-037putenv — the string becomes part of the environment (“altering the string shall change the environment”); caller must keep it alive and not modify/free it; a name with no = may unsetint putenv(char *string);./c.mdPOSIX.1-2024 putenv, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-038clearenv — clears the entire environment; glibc man page: uses/behaves like environ = NULL, with attendant leak caveats; POSIX RATIONALE: “considered but rejected”int clearenv(void);./c.mdman-pages 6.19 clearenv(3), man7.org; POSIX.1-2024 RATIONALE (rejected), pubs.opengroup.orgglibc (SVID/BSD; _DEFAULT_SOURCE)documented
PL-C-039ISO C process-model scope: only the termination family and system; freestanding has no main, no exit; freestanding program termination is implementation-defined—./c.mdISO C23 draft N3220 §5.1.2.2, open-std.orgISO C23 (N3220)documented
PL-C-040main→exit pipeline, layers 1–2: return from the initial call to main is equivalent to exit(main_value); reaching the closing } of main returns 0; runtime startup (crt1.o in glibc’s case) invokes main as a C function call with argc/argv and an initialized environ—./c.mdISO C17 draft N2310 §5.1.2.2.3; C23 draft N3220 §5.1.2.3.4, open-std.org; POSIX.1-2024 posix_spawn, pubs.opengroup.orgISO C17/C23 + POSIX.1-2024documented
PL-C-041main→exit pipeline, layers 3–4: glibc exit(3) runs atexit and on_exit handlers in reverse registration order — a handler that does not return abandons all remaining handlers and stdio flushing; flushes and closes all stdio streams, removes tmpfile files, then calls the _exit wrapper; _exit(2) invokes exit_group(2) (since glibc 2.3), terminating all threads; parent gets SIGCHLD; status stays readable by wait until reaped—./c.mdman-pages 6.19 exit(3), _exit(2) NOTES, man7.orgglibc 2.44/Linuxdocumented
PL-C-042POSIX exit-status plumbing: value made available to wait()/waitpid() is status & 0377 (low 8 bits); full value available from waitid() and the siginfo_t passed to a SIGCHLD handler; _Exit/_exit call no functions registered with atexit()/at_quick_exit(), no registered signal handlers, and open streams shall not be flushed—./c.mdPOSIX.1-2024 _Exit/_exit, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-043abort mechanics: glibc first unblocks SIGABRT, then raises it; handler catches and returns → restore default disposition and re-raise (abort cannot return); in practice glibc does not flush stdio; Linux: SIGABRT death follows the normal core-dump rules (RLIMIT_CORE, core_pattern)—./c.mdman-pages 6.19 abort(3), man7.orgglibc/Linuxdocumented
PL-C-044atexit/at_quick_exit ordering: reverse registration order; a handler registering another handler adds it to the front of the remaining list (late registrations run early); a handler runs as many times as it was registered; the two lists are distinct — dual registration is the documented pattern for exit-and-quick_exit cleanup; quick_exit is the sanctioned partial-cleanup exit for libraries—./c.mdman-pages 6.19 exit(3), man7.org; ISO C17 draft N2310 §7.22.4.2/§7.22.4.3–§7.22.4.4 (fn.), open-std.orgISO C17 + glibc/Linuxdocumented
PL-C-045Only the exit-family paths are graceful: death by unhandled signal runs no atexit/at_quick_exit/SIGABRT handlers and no stdio flush; flush explicitly before _Exit/abort if output must be delivered—./c.mdman-pages 6.19 exit(3), abort(3), _exit(2), man7.org; ISO C17 draft N2310 §7.22.4, open-std.orgISO C17 + glibc/Linuxdocumented
PL-C-046exec discards atexit/at_quick_exit/pthread_atfork registrations (“any functions previously registered … are no longer registered”) — an execing fork-child carries no parent-cleanup baggage—./c.mdPOSIX.1-2024 exec, pubs.opengroup.org; man-pages 6.19 exit(3) NOTES, man7.orgPOSIX.1-2024; glibcdocumented
PL-C-047Wait-status word vs exit status: child status truncated to 8 bits for wait()/waitpid() observers (WEXITSTATUS = least significant 8 bits; exit(256) indistinguishable from exit(0), exit(-1) reports as 255); normal-exit and signaled-exit are mutually exclusive encodings; no “exit code 137” in the word — 128+N is a shell convention applied after decoding; zero wait-status word means: returned 0 from main, called _exit/exit(0), or last thread of the process terminated—./c.mdPOSIX.1-2024 wait/_Exit, pubs.opengroup.org; man-pages 6.19 wait(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-048Thread vs process termination: pthread_exit(value_ptr) terminates only the calling thread; the process dies with exit status 0 when its last thread terminates; ISO C analog thrd_exit (<threads.h> optional via __STDC_NO_THREADS__, C11/C23 alike)—./c.mdPOSIX.1-2024 pthread_exit/wait, pubs.opengroup.org; C23 draft N3220 §7.28, open-std.orgPOSIX.1-2024 + ISO C11/C23documented
PL-C-049Signal-handling layers: glibc signal(3) is a wrapper over sigaction(2) with BSD semantics (glibc 2 and later; the kernel’s raw signal syscall is System V semantics); Linux delivers via the rt_sigaction syscall family with an internal SA_RESTORER trampoline; interrupted syscalls restart or fail with EINTR per SA_RESTART and the kernel’s per-syscall restartability list—./c.mdman-pages 6.19 signal(2), signal(7), sigaction(2), man7.orgglibc/Linuxdocumented
PL-C-050struct sigaction fields: void(*)(int) sa_handler, sigset_t sa_mask (additional signals blocked during handler execution), int sa_flags, void(*)(int, siginfo_t *, void *) sa_sigaction (storage may overlap with sa_handler — never use both)—./c.mdPOSIX.1-2024 sigaction, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-051SA_* flags: SA_RESTART (syscall restart instead of EINTR), SA_SIGINFO (three-argument handler; required for sigqueue payloads), SA_NOCLDSTOP, SA_NOCLDWAIT (auto-reap children — “do not transform children into zombies”; POSIX leaves SIGCHLD generation unspecified, Linux generates it), SA_NODEFER, SA_ONSTACK (alternate stack; cleared across exec)—./c.mdPOSIX.1-2024 sigaction, pubs.opengroup.org; man-pages 6.19 sigaction(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-052SIGKILL/SIGSTOP cannot be added to a mask or caught/ignored — “this restriction shall be enforced by the system without causing an error to be indicated”—./c.mdPOSIX.1-2024 sigaction, pubs.opengroup.org; man-pages 6.19 signal(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-053Masks and delivery: each thread has its own mask; a new thread inherits the creating thread’s mask and starts with an empty pending set; process-directed signals are delivered to any thread not blocking them, thread-directed (pthread_kill, raise) to the target; a standard signal pending for a process is pending once (duplicates collapse), real-time signals queue; a fatal thread-directed signal still terminates the whole process—./c.mdPOSIX.1-2024 pthread_create/pthread_sigmask/kill, pubs.opengroup.org; man-pages 6.19 pthreads(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-054Race-free waiting: sigsuspend(&waitmask) atomically swaps the mask and sleeps, closing the lost-wakeup window between “unblock” and “sleep”; sigwait-thread pattern — block the signal in all threads at startup, dedicated thread consumes it with sigwait (no handler, so no async-signal-safety restrictions)—./c.mdPOSIX.1-2024 sigsuspend/sigwait, pubs.opengroup.org; man-pages 6.19 pthreads(7), man7.orgPOSIX.1-2024documented
PL-C-055Handler→program communication: ISO C permits only assignment to a volatile sig_atomic_t, lock-free atomic objects, abort/_Exit/quick_exit (C23: lock-free <stdatomic.h> functions, constexpr objects), re-install via signal(sig,…); POSIX async-signal-safe list (write, _exit, sigaction, kill, sigsuspend, waitpid, …); sig_atomic_t guarantees atomic access only — no memory ordering, no multi-field updates—./c.mdC23 draft N3220 §7.14.1.1¶5, open-std.org; man-pages 6.19 signal-safety(7), man7.orgISO C23 + Linux man-pagesdocumented
PL-C-056Exec-time signal resets: default → default; SIG_IGN → SIG_IGN (except SIGCHLD, where ignore-or-default after exec is unspecified); caught → default; masks and pending sets carry into the new image’s initial thread; alternate stacks do not survive; Linux default actions per signal (term/core/stop/cont/ignore); query current action with sigaction(SIGX, NULL, &old)—./c.mdPOSIX.1-2024 exec, pubs.opengroup.org; man-pages 6.19 signal(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-057EINTR world: without SA_RESTART a caught signal interrupts blocking syscalls — they return -1/EINTR (waitpid, waitid, pause document it); SA_RESTART is per-signal opt-in and some operations are not restartable regardless (e.g. poll with a timeout historically); design for EINTR retries, block signals around critical waits, or use sigwait-style consumption—./c.mdPOSIX.1-2024 waitpid/waitid/pause, pubs.opengroup.org; man-pages 6.19 signal(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-058fork — child is an exact copy of the calling process except a unique PID and a different parent; multithreaded fork: the child contains a replica of the calling thread and its entire address space (possibly including mutex states) and shall only execute async-signal-safe operations until a successful execpid_t fork(void);./c.mdPOSIX.1-2024 fork, pubs.opengroup.org; man-pages 6.19 fork(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-059_Fork — like fork but never runs pthread_atfork handlers and is async-signal-safe (Issue 8’s MT-safe primitive)pid_t _Fork(void);./c.mdPOSIX.1-2024 fork, pubs.opengroup.orgPOSIX.1-2024 (Issue 8)documented
PL-C-060fork inheritance: address-space “copy” is copy-on-write (pages shared read-only, duplicated on first write); inherited — open file descriptors sharing the same open file descriptions (offsets and status flags shared, not copied), environment copy, signal mask, current/root directory, umask, resource limits, controlling terminal; not inherited — memory locks, per-process timers (alarm(2) among them), pending signals, process-associated record locks, semaphore adjustments, CPU-time counters—./c.mdman-pages 6.19 fork(2), man7.orgglibc/Linuxdocumented
PL-C-061exec family (Issue 8 exec page synopsis; suffix semantics: l list, v vector, e explicit envp, p PATH search, f open fd)int execl(const char *path, const char *arg0, ... /*, (char *)0 */); int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/); int execlp(const char *file, const char *arg0, ... /*, (char *)0 */); int execv(const char *path, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]); int execvp(const char *file, char *const argv[]); int fexecve(int fd, char *const argv[], char *const envp[]);./c.mdPOSIX.1-2024 exec, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-062exec suffix details: execvpe is a glibc GNU extension, not in POSIX.1-2024 (execvp plus explicit envp); PATH search uses the colon-separated PATH of the caller’s environment, default from confstr(_CS_PATH) (typically /bin:/usr/bin); execvpe searches using the caller’s PATH, not the envp argument’s—./c.mdPOSIX.1-2024 exec, pubs.opengroup.org; man-pages 6.19 exec(3), man7.orgPOSIX.1-2024; glibcdocumented
PL-C-063exec behavior: ENOEXEC fallback — execlp/execvp/execvpe on a file that would fail with ENOEXEC run it as a script via a command interpreter (execl(<shell path>, <name>, file, <args>, (char *)0)); there shall be no return from a successful exec; a failed exec of a real binary returns -1; conventional child failure statuses _exit(127) (command not found) / _exit(126) (found, not executable)—./c.mdPOSIX.1-2024 exec, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-064wait/waitpid — wait(&st) = waitpid(-1, &st, 0); pid semantics: -1 any child, > 0 that child, 0 same process group, < -1 process group |pid|; options WNOHANG (poll — returns 0 when no selected child has status), WUNTRACED, WCONTINUED (still [XSI] in POSIX.1-2024); without WNOHANG both are interruptible (EINTR, documented failure) and stat_loc is undefined when the call fails; multiple threads blocked in wait()-family calls: exactly one woken per child state changepid_t wait(int *stat_loc); / pid_t waitpid(pid_t pid, int *stat_loc, int options);./c.mdPOSIX.1-2024 wait (and 2.12 Status Information), pubs.opengroup.orgPOSIX.1-2024documented
PL-C-065waitid — selects by P_PID/P_PGID/P_ALL with WEXITED/WSTOPPED/WCONTINUED/WNOHANG/WNOWAIT (leave the child waitable); on success returns 0 and fills siginfo_t (si_code CLD_EXITED/CLD_KILLED/…, si_status); with WNOHANG and nothing to report returns 0 with empty info per the 2.12 Status Information rulesint waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);./c.mdPOSIX.1-2024 waitid (and 2.12 Status Information), pubs.opengroup.orgPOSIX.1-2024documented
PL-C-066Wait-status macros: WIFEXITED, WEXITSTATUS (low 8 bits), WIFSIGNALED, WTERMSIG, WCOREDUMP (newly standardized in POSIX.1-2024 — non-zero if core-image creation was attempted; non-zero does not guarantee a file exists, e.g. RLIMIT_CORE = 0), WIFSTOPPED/WSTOPSIG, WIFCONTINUED—./c.mdPOSIX.1-2024 wait, pubs.opengroup.org; man-pages 6.19 wait(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-067Linux kernel wait-status encoding — raw UAPI header claims (include/uapi/linux/wait.h, kernel-side macro definitions behind the W* decoding)—./c.mdman-pages 6.19 wait(2), man7.org (in lieu of include/uapi/linux/wait.h: git.kernel.org bot-protected, raw.githubusercontent rate-limited at retrieval)Linux kernel UAPIsource-limited
PL-C-068wait3/wait4 — BSD heritage, add rusage of the reaped child; wait3 removed from POSIX with Issue 7, survives in glibc under _DEFAULT_SOURCE feature-test rules; wait4 was never POSIX; new code uses waitid (or Linux wait4 where rusage matters)pid_t wait3(int *wstatus, int options, struct rusage *rusage); / pid_t wait4(pid_t pid, int *wstatus, int options, struct rusage *rusage);./c.mdman-pages 6.19 wait4(2), man7.orgglibc/Linuxdocumented
PL-C-069posix_spawn/posix_spawnp — return 0 on success (child PID stored through non-NULL pid), otherwise an error number as the return value (not -1/errno); errors after successful return are reported through the child’s exit status 127 (same convention as system)int posix_spawn(pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]); / int posix_spawnp(pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]);./c.mdPOSIX.1-2024 posix_spawn, pubs.opengroup.orgPOSIX.1-2024documented
PL-C-070posix_spawn mechanics: file_actions (posix_spawn_file_actions_init/addopen/adddup2/addclose applied in order in the child between spawn and exec) are the fd-redirection mechanism without forking; attrs set POSIX_SPAWN_SETSIGMASK, POSIX_SPAWN_SETSIGDEF, POSIX_SPAWN_SETPGROUP, POSIX_SPAWN_RESETIDS (and glibc’s POSIX_SPAWN_USEVFORK); glibc implements spawn without a full fork (before 2.24 vfork(2)/`clone(CLONE_VMCLONE_VFORK)`; current per-attribute/file_actions paths) — sidesteps multithreaded-fork hazards; prefer it in threaded code—./c.mdPOSIX.1-2024 posix_spawn, pubs.opengroup.org; man-pages 6.19 posix_spawn(3) VERSIONS/NOTES, man7.orgPOSIX.1-2024; glibc 2.44
PL-C-071system (POSIX contract): child created with fork() invokes the shell as execl(<shell path>, "sh", "-c", "--", command, (char *)0); parent ignores SIGINT and SIGQUIT and blocks SIGCHLD while waiting; return -1 (child could not be created/errno set), 127-style (interpreter could not execute), otherwise the shell’s wait status — decode with the W* macros, never compare directly; command == NULL → nonzero iff a shell exists; glibc uses /bin/sh; concatenating untrusted data into command is command injection — the argv-based interfaces (execve family, posix_spawn) pass arguments verbatimint system(const char *command);./c.mdPOSIX.1-2024 system, pubs.opengroup.org; man-pages 6.19 system(3), man7.orgPOSIX.1-2024; glibcdocumented
PL-C-072popen/pclose — "r": child stdout → readable stream; "w": child stdin ← writable stream; POSIX.1-2024 specifies FD_CLOFORK is cleared on both ends; pclose waits like waitpid on the shell process and returns its wait status, -1/ECHILD if the status is gone; glibc mode 'e' (since glibc 2.9) sets FD_CLOEXEC on the underlying descriptor; popen streams are fully buffered — fflush/pclose ordering matters for interleaved writesFILE *popen(const char *command, const char *mode); / int pclose(FILE *stream);./c.mdPOSIX.1-2024 popen, pubs.opengroup.org; man-pages 6.19 popen(3), man7.orgPOSIX.1-2024; glibc ≥ 2.9documented
PL-C-073Pipe redirection pattern: close unused pipe ends in both processes (a stray write end keeps readers from EOF); the child’s error path uses _exit (no parent atexit handlers, no flush of duplicated stdio buffers); dup2(fds[1], STDOUT_FILENO) closes the new fd’s previous target atomically; an exec-less child closes everything it does not need; Linux pipe capacity is finite (default 64 KiB, queryable via fcntl(fd, F_GETPIPE_SZ)) — readers and writers must progress in parallel or deadlock—./c.mdman-pages 6.19 pipe(2), fcntl(2), man7.orgLinux; POSIX.1-2024documented
PL-C-074SIGCHLD, zombies, and reaping: a terminated-but-unwaited child is a zombie (kernel keeps the exit status until reaped); strategies — wait for each spawned child; SIGCHLD handler whose entire body is while (waitpid(-1, &st, WNOHANG) > 0); (both calls async-signal-safe); SA_NOCLDWAIT or SIGCHLD set to SIG_IGN auto-reaps children (POSIX unspecified whether SIGCHLD is generated; Linux generates it); block SIGCHLD everywhere and reap in a dedicated sigwait thread; unreaped zombies are a resource leak (PIDs are finite); a parent that exits leaves zombies to init or the nearest subreaper—./c.mdPOSIX.1-2024 sigaction/wait, pubs.opengroup.org; man-pages 6.19 sigaction(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-075fd hygiene across fork/exec: O_CLOEXEC on creation (Linux) or fcntl(fd, F_SETFD, FD_CLOEXEC) (POSIX) auto-closes descriptors on successful exec; posix_spawn_file_actions_addclose/adddup2 shape child fds with no fork window; close_range closes everything from first to last in the pre-exec child (since Linux 5.9); Issue 8 introduces FD_CLOFORK (close-on-fork); the two failure shapes: a leaked fd into an exec’d child keeps pipe EOFs pending in unrelated descendants, a double-close can silently steal a reused fd number from an unrelated objectint close_range(unsigned int first, unsigned int last, int flags);./c.mdPOSIX.1-2024 popen (FD_CLOFORK)/fcntl, pubs.opengroup.org; man-pages 6.19 close_range(2), fcntl(2), man7.orgPOSIX.1-2024; Linux ≥ 5.9 (close_range)documented
PL-C-076Wait timeouts (POSIX has no timed wait for children): alarm(n) + blocked SIGALRM + waitpid, kill-then-reap at the alarm; polling waitpid(pid, &st, WNOHANG) on a timer tick, kill at the deadline, reap; sigtimedwait/signalfd (Linux) wait for SIGCHLD with a timeout instead of polling; a timed-out kill surfaces as WIFSIGNALED with WTERMSIG == SIGKILL — not as an error from wait; the deadline path must still reap—./c.mdPOSIX.1-2024 alarm/wait, pubs.opengroup.org; man-pages 6.19, man7.orgPOSIX.1-2024; Linuxdocumented
PL-C-077pthread lifecycle: returning from start_routine is as if pthread_exit(return_value) — except in the thread main ran in, where returning is as if exit(return_value) (process death); pthread_join reaps the thread (joinable by default); pthread_detach or PTHREAD_CREATE_DETACHED attr makes it self-releasing; an unjoined, undetached joinable thread leaks its resources (the thread-level zombie); new threads inherit the creating thread’s mask with an empty pending setint pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg); / int pthread_join(pthread_t thread, void **retval); / int pthread_detach(pthread_t thread);./c.mdPOSIX.1-2024 pthread_create/pthread_join/pthread_detach, pubs.opengroup.org; man-pages 6.19 pthreads(7), man7.orgPOSIX.1-2024; glibc (native Linux threads)documented
PL-C-078pthread_exit/pthread_cancel: pthread_exit runs cancellation cleanup handlers (reverse order) and TSS destructors, and does not release application-visible process resources (mutexes, fds) nor perform process-level cleanup (no atexit() routines) — the process dies only when its last thread exits; pthread_cancel is honored per the target’s cancelability state (PTHREAD_CANCEL_ENABLE default / DISABLE) and type (deferred default / asynchronous) at cancellation points (read, write, waitpid, nanosleep, pthread_join, …); pthread_cleanup_push/pthread_cleanup_pop run pushed handlers on cancel/early exit; canceling an already-terminated (zombie) thread is not an error_Noreturn void pthread_exit(void *value_ptr); / int pthread_cancel(pthread_t thread);./c.mdPOSIX.1-2024 pthread_exit/pthread_cancel/pthread_cleanup_push, pubs.opengroup.org; man-pages 6.19 pthread_exit(3), pthread_cancel(3), man7.orgPOSIX.1-2024documented
PL-C-079fd ownership across fork/exec: fork duplicates fd numbers; both copies refer to one open file description (one file offset, one status-flag set — lseek in one is visible to the other; closing your copy does not affect the other’s); no language-level ownership protection: double close may close a number that open/accept reissued (corrupting an unrelated stream), every fd without FD_CLOEXEC crosses exec, a forgotten parent write end prevents EOF (child output loop blocks forever)—./c.mdman-pages 6.19 fork(2), man7.orgglibc/Linuxdocumented
PL-C-080stdio buffering × fork/exec: three modes — unbuffered (stderr by default), line buffered (stdout on a terminal), full/block buffered (stdout to file or pipe) — set at startup; fork copies the stdio buffers (unflushed parent data flushes again from the child → output appears twice; fix: flush before forking, or _exit in the child); exec discards unflushed stdio data; setvbuf(stdout, NULL, _IOLBF, 0) (or fflush at the seams) fixes interleaving with write(STDOUT_FILENO, …); all stdio functions are async-signal-unsafe (shared-buffer corruption)—./c.mdman-pages 6.19 stdio(3), stdin(3), setbuf(3), signal-safety(7), man7.orgglibc/POSIXdocumented
PL-C-081Deadlocks, races, and leaks: after fork in a threaded parent the child has one thread — mutexes held by vanished threads (glibc malloc/stdio locks) stay locked forever, so malloc/printf/PATH-handling in the pre-exec child can deadlock (modern answers: posix_spawn, _Fork, obsolescent pthread_atfork prepare handlers); the exit race — exit runs atexit handlers while other threads still run (concurrent atexit unspecified to succeed; data race = UB); an atexit handler waiting on a mutex held by a terminated thread blocks exit forever; fd leaks on error paths exhaust RLIMIT_NOFILE; zombie accumulation; unjoined threads leak until process exit; allocated memory is reclaimed wholesale by the OS—./c.mdPOSIX.1-2024 fork/exit/pthread_exit, pubs.opengroup.org; man-pages 6.19 (fork, exit, pthread_exit), man7.orgPOSIX.1-2024; glibc/Linuxdocumented
PL-C-082pthread_atfork: prepare handlers run before fork processing, parent/child handlers after it in the respective processes; POSIX.1-2024 marks it obsolescent (Austin Group Defect 851); exec clears its registrations; direction of travel: _Fork() (no fork handlers, async-signal-safe) for raw process creation, posix_spawn for exec-shaped needs, explicit child-side code instead of magic re-initializationint pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));./c.mdPOSIX.1-2024 pthread_atfork (obsolescent per Issue 8 change history), pubs.opengroup.org; man-pages 6.19 pthread_atfork(3), man7.orgPOSIX.1-2024; glibcdocumented