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: ./cpp.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 C++23 — the C++23 DIS N4950 (open-std.org) is the baseline for every signature and normative quote; C++26 deltas were checked against the current working draft at eel.is/c++draft (generated 2026-08-23). Implementation layer: GCC 14.2.0 (Debian) installed libstdc++ headers (<cstdlib>, <stop_token>); glibc current stable 2.44 (sourceware.org/glibc). OS layer that C++ programs call directly: POSIX.1-2024 / Issue 8 (pubs.opengroup.org/onlinepubs/9799919799) and Linux man-pages 6.19 (man7.org, dated 2026-06-05). en.cppreference.com (the signature reference of record for this part) was Cloudflare-blocked from this environment at retrieval time (2026-10-13); every signature was therefore transcribed directly from the standard drafts named above (N4950 / eel.is anchors) — the stronger primary source. The affected row (PL-CPP-069, the std::jthread destructor’s exact clause wording) is the ledger’s single source-limited entry: the working-draft section fetched verified the class-level contract (“automatically join”, [thread.jthread.class]) without transcribing the destructor paragraph.

Status definitions.

  • documented — the claim is documented in ./cpp.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 ./cpp.md, but the exact primary wording was unreachable at retrieval time; anchored to the working-draft class-level contract instead (cppreference Cloudflare-blocked; destructor paragraph not transcribed).
  • not-started / researched — defined by the repo ledger convention; unused in this ledger (Remaining: 0).

Rollup: Inventoried: 75 / Documented: 74 / Remaining: 0 / Source-limited: 1

Section layout: §1 → PL-CPP-001–009 · §2 → PL-CPP-010–041 · §3 → PL-CPP-042–050 · §4 → PL-CPP-051–054 · §5 → PL-CPP-055–064 · §6 → PL-CPP-065–075.

IDLanguage/API or behaviorCanonical signatureDocumentation pathAuthoritative referenceVersion/platform scopeStatus
PL-CPP-001§1.1 main: exactly one global-scope main in a hosted program; return v destroys main’s automatic objects then calls std::exit(v); falling off the end is return 0; calling std::exit during destruction of a static/thread object is UB—./cpp.mdC++23 DIS N4950 [basic.start.main] (§6.9.3.1), open-std.org; eel.is/c++draft [basic.start.main] (C++26 UB anchor)ISO C++ (C++98→C++23), hosteddocumented
PL-CPP-002§1.2 Storage durations (static/thread/automatic/dynamic) decide what std::exit destroys; dynamic initialization deferred and ordered by rule; block-scope static/thread_local initialized on first pass with thread-safe concurrent wait (magic statics)—./cpp.mdC++23 DIS N4950 [basic.stc] (§6.8.6), [basic.start.dynamic], [stmt.dcl] (§8.4), open-std.orgISO C++ (C++98→C++23; thread_local/magic statics C++11+)documented
PL-CPP-003§1.3 Static initialization order fiasco: cross-TU dynamic init indeterminately sequenced; standard-shaped cure is the construct-on-first-use idiom (function-local static, defined order + thread-safe init)—./cpp.mdC++23 DIS N4950 [basic.start.dynamic], [stmt.dcl] (§8.4), open-std.orgISO C++ (C++98→C++23; thread-safe local statics C++11+)documented
PL-CPP-004§1.4 RAII and the special member functions: deterministic reverse-of-construction destruction; destructors run without scope exit via unwinding; destructor contract for termination paths (no escaping exceptions, no unbounded blocking, no assumptions about other statics once std::exit runs)—./cpp.mdC++23 DIS N4950 [basic.start.term] (UB rules), [except.terminate], open-std.orgISO C++ (C++98→C++23)documented
PL-CPP-005§1.5 Exceptions: throw completes the throw-expression then unwinds (destructors of completely/partially constructed automatic objects in reverse); destructor exiting by exception during unwinding ⇒ std::terminate; noexcept violation ⇒ std::terminate with implementation-defined unwinding; exceptions escaping main ⇒ std::terminate before std::exit is ever considered—./cpp.mdC++23 DIS N4950 [except.throw] (§14.3), [except.ctor], [except.spec] (§14.5), [except.terminate], open-std.orgISO C++ (C++98→C++23; std::uncaught_exceptions() count form C++17)documented
PL-CPP-006§1.6 Normal vs exceptional termination vocabulary: normal = return from main / std::exit / std::quick_exit / std::_Exit; abnormal = std::abort and everything routing through std::terminate (default handler calls abort()); external signal death is not a language category; POSIX parent-side classes WIFEXITED/WIFSIGNALED/WIFSTOPPED—./cpp.mdC++23 DIS N4950 [support.start.term], [except.terminate], open-std.org; POSIX.1-2024 wait status classes, pubs.opengroup.orgISO C++ (C++98→C++23; quick_exit/_Exit C++11+) + POSIX.1-2024documented
PL-CPP-007§1.7 UB/unspecified/implementation-defined rows in scope: std::exit during static/thread destruction (UB); re-entering destroyed block-scope statics during static destruction (UB, p4); library use racing static destruction (UB, p6 + Note 3); non-signal-safe handler evaluations (UB); data races (UB); atexit/at_quick_exit success without happens-before (unspecified); unwinding before std::terminate (impl-defined); host status value, deferred dynamic init, _Exit/abort stream disposition, main linkage, freestanding main (impl-defined)—./cpp.mdC++23 DIS N4950 [basic.start.main], [basic.start.term] p4/p6, [support.signal] p3, [intro.races], [support.start.term] p6/p9/p10, [except.terminate] p2, [basic.start.dynamic] p3, open-std.orgISO C++ (C++11→C++23)documented
PL-CPP-008§1.8 Version deltas changing in-scope semantics: C++11 (thread_local; quick_exit/at_quick_exit; magic statics; terminate on thread-function exception; std::future/std::async blocking-destructor rule; mutex/RAII lock types); C++14/17 (uncaught_exceptions() count; scoped_lock; shared_mutex); C++20 (jthread; stop tokens; atomic_ref; atomic wait/notify); C++23 no semantic change; C++26 working-draft deltas flagged—./cpp.mdC++23 DIS N4950 §6.8.6.3, §17.5, §8.4, §33.4, §33.10, §14.6.3, §33.6.5, §33.3, open-std.org; eel.is/c++draft [support.signal], [basic.start.main]ISO C++ C++11 → C++23 (C++26 working draft checked)documented
PL-CPP-009§1.9 Misconceptions corrected: throwing from a destructor only safe when nothing is unwinding; std::exit is the second half of returning from main and skips all live automatic objects; destructors are implicitly noexcept(true); static init may be deferred past main; returning from main does not wait for threads ([basic.start.term]p6 race); throwing from a signal handler is UB outright—./cpp.mdC++23 DIS N4950 [except.terminate] (1.4), [support.signal] (3.4), [basic.start.term] p6, [basic.start.dynamic] p3, open-std.orgISO C++ (C++11→C++23)documented
PL-CPP-010exit (<cstdlib>): calling thread’s thread_local dtors → static dtors + atexit (reverse, interleaved) → C streams flushed & closed, tmpfile files removed → host status; automatic objects not destroyed[[noreturn]] void exit(int status); — not noexcept./cpp.mdC++23 DIS N4950 [support.start.term] (§17.5, effects p9), open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-011_Exit (<cstdlib>): terminates without executing destructors of any storage duration and without calling functions passed to atexit(); stream disposition implementation-defined; marked signal-safe[[noreturn]] void _Exit(int status) noexcept;./cpp.mdC++23 DIS N4950 [support.start.term], open-std.org; C17 §7.22.4.5 (stream disposition)C++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-012quick_exit (<cstdlib>): runs only at_quick_exit handlers (reverse order) on the calling thread; destroys no objects; then calls _Exit(status); exception in a registered handler ⇒ std::terminate; signal-safe iff the registered functions are[[noreturn]] void quick_exit(int status) noexcept;./cpp.mdC++23 DIS N4950 [support.start.term], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-013abort (<cstdlib>): C semantics — raises SIGABRT; no destructors, no atexit; stream flush/close implementation-defined; marked signal-safe[[noreturn]] void abort() noexcept;./cpp.mdC++23 DIS N4950 [support.start.term], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-014atexit (<cstdlib>): register normal-termination handler; ≥32 registrations; zero on success; called once per registration; unspecified to succeed unless it happens-before exitint atexit(c-atexit-handler* f) noexcept; / int atexit(atexit-handler* f) noexcept; (two overloads)./cpp.mdC++23 DIS N4950 [support.start.term], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-015at_quick_exit (<cstdlib>): same contract for the quick_exit list (≥32); the two lists are distinct (Note 5)int at_quick_exit(c-atexit-handler* f) noexcept; / int at_quick_exit(atexit-handler* f) noexcept;./cpp.mdC++23 DIS N4950 [support.start.term], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-016EXIT_SUCCESS / EXIT_FAILURE (<cstdlib>): consumed by exit/_Exit/quick_exit; the host-status mapping is implementation-defined#define EXIT_SUCCESS /* see below */ (+ EXIT_FAILURE)./cpp.mdC++23 DIS N4950 [cstdlib.syn] (§17.2.2), [support.start.term] p9, open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-017getenv (<cstdlib>): borrowed pointer into the environment; must not be modified by the program; calls shall not introduce a data race provided that nothing modifies the environment (note names POSIX setenv/putenv as the modifiers)char* getenv(const char* name);./cpp.mdC++23 DIS N4950 [support.runtime] (§17.13.1), open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-018system (<cstdlib>): implementation-defined command processor; POSIX sharpens to /bin/sh -c + full wait status (§5.1)int system(const char* string);./cpp.mdC++23 DIS N4950 [cstdlib.syn], open-std.org; POSIX.1-2024 system, pubs.opengroup.org; man-pages 6.19 system(3), man7.orgC++98+ / libstdc++; POSIX.1-2024; Linuxdocumented
PL-CPP-019std::terminate (<exception>): enters the current terminate handler; must not return; unwinding behavior depends on the trigger situation[[noreturn]] void terminate() noexcept;./cpp.mdC++23 DIS N4950 [exception.terminate] (§17.9.5), open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-020terminate_handler (<exception>): required behavior terminate without returning; default behavior — the implementation’s default terminate_handler calls abort()using terminate_handler = void(*)();./cpp.mdC++23 DIS N4950 [exception.terminate], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-021set_terminate (<exception>): install handler; returns the previous one; unspecified whether a null pointer designates the default handlerterminate_handler set_terminate(terminate_handler f) noexcept;./cpp.mdC++23 DIS N4950 [exception.terminate], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-022get_terminate (<exception>): current handler; may be a null pointer value (Note)terminate_handler get_terminate() noexcept;./cpp.mdC++23 DIS N4950 [exception.terminate], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-023signal (<csignal>): install SIG_DFL/SIG_IGN/handler; a call to signal synchronizes with any resulting handler invocation; signal-safe when invoked from a handler with the same sig; contents of <csignal> identical to C <signal.h>signal-handler* signal(int sig, signal-handler* func); with extern "C" using signal-handler = void(int); (exposition-only name)./cpp.mdC++23 DIS N4950 [csignal.syn] (§17.13.4), [support.signal], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-024raise (<csignal>): send to the executing program/thread; runs the handler synchronouslyint raise(int sig);./cpp.mdC++23 DIS N4950 [csignal.syn], open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-025sig_atomic_t (<csignal>): integer type accessed atomically; the volatile std::sig_atomic_t carve-out in the data-race rulesusing sig_atomic_t = /* see below */;./cpp.mdC++23 DIS N4950 [csignal.syn], [intro.races]/[intro.progress] carve-out, open-std.orgC++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-026std::thread (<thread>): new thread of execution running invoke(f, args...); “if the invocation of invoke terminates with an uncaught exception, terminate is invoked”class thread; ctor template<class F, class... Args> explicit thread(F&& f, Args&&... args);./cpp.mdC++23 DIS N4950 [thread.thread.constr] (§33.4), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-027std::thread::~thread: “If joinable(), invokes terminate. Otherwise, has no effects.” — the joinable-destructor rule~thread();./cpp.mdC++23 DIS N4950 [thread.thread.destr], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-028thread::join / thread::detach / thread::joinable: join blocks until the thread completes then releases state (error conditions include no_such_process, invalid_argument); detach releases without waiting; joinable queriesvoid join(); void detach(); bool joinable() const noexcept;./cpp.mdC++23 DIS N4950 [thread.threads] (§33.4), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-029thread::hardware_concurrency: hint — number of concurrent threads supported; may return 0static unsigned int hardware_concurrency() noexcept;./cpp.mdC++23 DIS N4950 [thread.threads], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-030std::jthread (<thread>): “the same as class thread with the additional abilities to provide a stop_token to the new thread of execution, make stop requests, and automatically join”class jthread; with stop_source get_stop_source() noexcept; stop_token get_stop_token() const noexcept; bool request_stop() noexcept;./cpp.mdC++23 DIS N4950 [thread.jthread.class] (§33.4), open-std.orgC++20+ / libstdc++ (GCC 14.2.0; <stop_token> confirmed)documented
PL-CPP-031std::stop_token (<stop_token>): shared-ownership handle on a stop state; the query side of cooperative cancellationbool stop_requested() const noexcept; bool stop_possible() const noexcept;./cpp.mdC++23 DIS N4950 [thread.stoptoken] (§33.3), open-std.orgC++20+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-032std::stop_source (<stop_token>): request side; first request_stop() wins, associated callbacks firebool request_stop() noexcept; stop_token get_token() const noexcept; bool stop_requested() const noexcept;./cpp.mdC++23 DIS N4950 [thread.stoptoken], open-std.orgC++20+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-033std::stop_callback (<stop_token>): RAII callback registration; invoked on first stop request by any associated sourcetemplate<class C> class stop_callback; with template<class C> stop_callback(stop_token, C&&);./cpp.mdC++23 DIS N4950 [thread.stoptoken], open-std.orgC++20+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-034std::async (<future>): creates a shared state and launches — launch::async new thread; launch::deferred stored, run lazily in the thread calling get/wait; no policy (or async|deferred) ⇒ implementation choosestemplate<class F, class... Args> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(launch policy, F&& f, Args&&... args); (+ overload without policy)./cpp.mdC++23 DIS N4950 [futures.async] (§33.10), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-035std::future (<future>): get returns the stored value or rethrows the stored exception, valid()==false afterwards; destructor releases the shared state; for a state created by async with launch::async, the last future “shall block until the associated thread has completed, as if joined”R future::get(); void wait() const; template<class Rep, class Period> future_status wait_for(...) const; wait_until(...) ~future();./cpp.mdC++23 DIS N4950 [futures.async], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-036std::shared_future (<future>): many-consumer view; get() may be called repeatedly; no blocking-destructor rule beyond releasing the last referenceshared_future<R>(future<R>&&);./cpp.mdC++23 DIS N4950 [futures] (§33.10), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-037std::mutex (<mutex>): blocking exclusive ownership; lock may throw system_error (resource_deadlock_would_occur “might be observed”, not guaranteed); not recursive (recursive lock by the owner is UB)void lock(); bool try_lock() noexcept; void unlock(); using native_handle_type = /* implementation-defined */;./cpp.mdC++23 DIS N4950 [thread.mutex] (§33.6), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-038std::lock_guard (<mutex>): strictly scoped RAII lock — m.lock() in the constructor, m.unlock() in the destructor; zero overhead, no unlock() pathexplicit lock_guard(mutex_type& m); lock_guard(mutex_type& m, adopt_lock_t); (copy deleted)./cpp.mdC++23 DIS N4950 [thread.mutex], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-039std::unique_lock (<mutex>): movable lock with owns_lock() state; the lock type required for std::condition_variable waitsunique_lock() noexcept; explicit unique_lock(mutex_type& m); unique_lock(mutex_type& m, defer_lock_t) noexcept; unique_lock(mutex_type& m, try_to_lock_t); unique_lock(mutex_type& m, adopt_lock_t); + timed ctors./cpp.mdC++23 DIS N4950 [thread.mutex], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-040std::scoped_lock (<mutex>): acquires several mutexes with deadlock-avoidance (std::lock algorithm) in one scope-exit releaseexplicit scoped_lock(MutexTypes&... m); explicit scoped_lock(adopt_lock_t, MutexTypes&... m);./cpp.mdC++23 DIS N4950 [thread.mutex], open-std.orgC++17+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-041§2.7 What the C++ standard library does not have: no fork/exec/wait/popen-with-fds, no sigaction/sigprocmask/sigwait, no pipe/dup2/fcntl, no timed child-wait, no process-group control; the only process-creation primitive is std::system, the only environment reader std::getenv; POSIX layer used from C++—./cpp.mdC++23 DIS N4950 [cstdlib.syn] (complete standard process surface), open-std.org; POSIX.1-2024, pubs.opengroup.orgISO C++ (C++98→C++23) + POSIX.1-2024documented
PL-CPP-042§3.1 Startup pipeline: implementation runs dynamic init (timing impl-defined), invokes main on the main thread, return v ⇒ destroy main’s automatics then std::exit(v); POSIX loader hands argc/argv/environ via crt1.o; glibc std::exit = C exit (atexit/on_exit reverse, stdio flushed, _exit wrapper); Linux _exit(2) → exit_group(2) — all threads die, parent gets SIGCHLD + status—./cpp.mdC++23 DIS N4950 [basic.start.main] p5, open-std.org; POSIX.1-2024 exec, posix_spawn, pubs.opengroup.org; man-pages 6.19 exit(3), _exit(2), man7.orgISO C++ (C++98+) + POSIX.1-2024; Linuxdocumented
PL-CPP-043§3.2 std::exit exact sequence: (9.1) thread-duration objects of the current thread destroyed, then static-duration objects destroyed + atexit functions called (automatic objects explicitly not destroyed); (9.2) all open C streams with unwritten buffered data flushed, all open C streams closed, tmpfile files removed; (9.3) host status (0/EXIT_SUCCESS/EXIT_FAILURE/other — impl-defined mapping); mechanism: unwinding is per-thread/per-stack, so exit destroys only the implementation-managed registries and atexit runs on the calling thread while others may still run—./cpp.mdC++23 DIS N4950 [support.start.term] p9; [basic.start.term] p6, open-std.orgISO C++ (C++98+; thread_local step C++11+)documented
PL-CPP-044§3.3 atexit/at_quick_exit registration contracts: reverse-order invocation; a function registered n times runs n times; handler-registering-handler LIFO to the end; exception in a handler ⇒ std::terminate (trigger (1.7)); the two lists are distinct (Note 5); unspecified to succeed without happens-before; glibc ≥32 minimum grows dynamically and execve(2) clears both lists—./cpp.mdC++23 DIS N4950 [support.start.term] (p6, p9 + Note 5), open-std.org; man-pages 6.19 exit(3), man7.orgC++98+ (at_quick_exit C++11+) / glibcdocumented
PL-CPP-045§3.4 std::quick_exit / std::_Exit no-destructor paths: quick_exit runs at_quick_exit handlers on the calling thread which “cannot rely on the identity of objects with thread storage duration” (Note 6), destroys no objects, then _Exit; _Exit runs nothing, stream flush/close impl-defined; POSIX _exit shall not flush open streams; exit status truncated to low 8 bits for wait()/waitpid() (status & 0377; exit(256)→0, exit(-1)→255), full value via waitid()/SIGCHLD siginfo_t—./cpp.mdC++23 DIS N4950 [support.start.term] (Note 6), open-std.org; POSIX.1-2024 _Exit/_exit, pubs.opengroup.org; man-pages 6.19 wait(2) WEXITSTATUS, man7.orgC++11+; POSIX.1-2024; Linuxdocumented
PL-CPP-046§3.5 Static-object destruction at exit: destruction reverse of construction completion (strongly-happens-before rule), per TU for ordered initializations; statically initialized objects destroyed as if dynamically initialized first; thread_local destruction of the exiting thread strongly happens before static destruction; atexit interleaving — one LIFO list weaving objects and handlers; UB edges: re-entry through a destroyed block-scope static’s declaration (p4) and library use not happening-before destruction completion (p6 + Note 3: “Terminating every thread before a call to std::exit … is sufficient, but not necessary”); throwing destructor ⇒ std::terminate—./cpp.mdC++23 DIS N4950 [basic.start.term] p1–p6 + Note 3, open-std.orgISO C++ (C++11+)documented
PL-CPP-047§3.6 std::terminate triggers in scope: (1.2) no handler found (incl. escaping main); (1.3) noexcept outermost block; (1.4) destructor throws during unwinding; (1.5) static/thread init throws; (1.6) static/thread destruction throws; (1.7) atexit/at_quick_exit handler throws; (1.8) re-throw; with nothing handled; (1.10) thread initial function throws; (1.12) joinable thread destructor/move-assignment; (1.13) condition-variable wait postcondition failure. Unwind rules: no-handler and noexcept cases impl-defined (possibly partial); all other situations no unwinding; default handler calls abort() → SIGABRT death (134 shell arithmetic on Linux — platform observation)—./cpp.mdC++23 DIS N4950 [except.terminate] (§14.6.2, triggers (1.2)–(1.13), p2), open-std.orgISO C++ (C++98+; thread triggers C++11+)documented
PL-CPP-048§3.7 Threads + exit: std::exit from any thread is legal, runs the exit sequence on the calling thread while others keep executing — library use not happening-before destruction completion is UB ([basic.start.term]p6); a joinable std::thread destroyed at exit (e.g. unwound return from main) calls std::terminate; atexit-handler deadlock on a mutex held by a dead/never-joined thread blocks exit forever (thread death releases no locks)—./cpp.mdC++23 DIS N4950 [basic.start.term] p6 + Note 3, open-std.org; POSIX.1-2024 pthread_exit, pubs.opengroup.orgISO C++ (C++11+); POSIX.1-2024documented
PL-CPP-049§3.8 Consolidated termination-path matrix: what runs on each path (automatic dtors / current-thread thread_local dtors / static dtors + atexit / at_quick_exit / C streams flushed / status to host) for return v from main, std::exit, std::quick_exit, std::_Exit/_exit, std::abort, uncaught-exception terminate→abort, death by external signal; Linux mechanisms exit_group(2), raise(SIGABRT) + core per RLIMIT_CORE—./cpp.mdC++23 DIS N4950 [support.start.term], open-std.org; man-pages 6.19 exit(3), _exit(2), abort(3), man7.orgISO C++ (C++11+); POSIX.1-2024; Linuxdocumented
PL-CPP-050§3.9 Process vs thread vs child: the exit family terminates the whole process from any thread (Linux exit_group(2), all threads die); a thread cannot exit the process without them; pthread_exit from a C++ thread frame skips C++ destructors — a stack teardown, not unwinding (do not mix; use thread-function returns); C++ has no std::child type — own the pid_t and the wait, wrap in RAII—./cpp.mdC++23 DIS N4950 [basic.start.term], open-std.org; POSIX.1-2024 pthread_exit, pubs.opengroup.org; man-pages 6.19 _exit(2) (exit_group), man7.orgISO C++ (C++11+); POSIX.1-2024; Linuxdocumented
PL-CPP-051§4.1 Signal layering: the entire C++ standard signal API is <csignal>, “the same as the C standard library header <signal.h>” (std::signal, std::raise, std::sig_atomic_t, SIG* macros); no C++ mask API, queueing, sigwait, or per-thread delivery rules; everything beyond is the POSIX layer used from C++ (C linkage, semantics in c.md §4)—./cpp.mdC++23 DIS N4950 [csignal.syn] (§17.13.4), open-std.orgISO C++ (C++98+); POSIX.1-2024documented
PL-CPP-052§4.2 Signal-safe evaluation list ([support.signal]p3): an evaluation is signal-safe unless it calls any standard-library function (except plain lock-free atomics and explicitly signal-safe functions — _Exit, quick_exit, abort, signal(sig, …) with the same sig), accesses thread-storage objects, uses dynamic_cast, throws, enters a try-block/function-try-block, dynamically initializes a static, or waits on the magic-statics lock; violation ⇒ UB; RAII destructors in the interrupted frame are not run by signal delivery—./cpp.mdC++23 DIS N4950 [support.signal] p3 (§17.13.5), open-std.orgISO C++ (C++11+)documented
PL-CPP-053§4.3 Handler→program communication the standard blesses: volatile std::sig_atomic_t data-race carve-out with the happens-before sandwich ([intro.races]/[intro.progress]); plain lock-free atomics (is_lock_free()/atomic_is_lock_free hold — no waiting/notify in a handler, [support.signal]p2); std::exit is not signal-safe — the safe terminators are abort/_Exit/quick_exit; POSIX widens to the async-signal-safe list (self-pipe write(2) works from a C++ handler)—./cpp.mdC++23 DIS N4950 [intro.races]/[intro.progress], [support.signal] p2, open-std.org; man-pages 6.19 signal-safety(7), man7.orgISO C++ (C++11+); POSIX.1-2024; Linuxdocumented
PL-CPP-054§4.4 POSIX signal layer from C++: process-directed signals can run a handler on any std::thread; the standard pattern is the sigwait-thread pattern — block SIGINT/SIGTERM in all threads at startup (pthread_sigmask before spawning), a dedicated std::thread loops on sigwait with no handler context, so the full C++ library is usable in that loop; RAII does not compose with handlers (no unwinding on signal delivery); SIGABRT caught-and-returned ⇒ glibc restores the default disposition and re-raises, so std::abort cannot return regardless of handlers—./cpp.mdPOSIX.1-2024 pthread_sigmask, sigwait, pubs.opengroup.org; man-pages 6.19 signal-safety(7), abort(3), man7.orgPOSIX.1-2024; Linux (glibc 2.44)documented
PL-CPP-055§5.1 std::system under POSIX: the child invokes the shell as execl(<shell path>, "sh", "-c", "--", command, (char *)0) (glibc <shell path> = /bin/sh); the return value is the full wait status word — decode with WIFEXITED/WEXITSTATUS/WIFSIGNALED/WTERMSIG (never compare directly); -1 = child could not be created; while waiting the parent ignores SIGINT/SIGQUIT and blocks SIGCHLD (may miss its own fatal signal — “check the return value”)—./cpp.mdPOSIX.1-2024 system, pubs.opengroup.org; man-pages 6.19 system(3), man7.orgC++98+ / libstdc++; POSIX.1-2024; Linux (/bin/sh)documented
PL-CPP-056§5.2 Injection — the string API’s permanent hazard: std::system interprets its argument as a shell program; concatenating untrusted data (filenames with metacharacters, env-derived strings, user input) is command injection; safe path is the argv-array interfaces (execv-family, posix_spawn) passing arguments verbatim with no shell layer; reserve system/popen for trusted command lines, quote defensively (shlex-style quoting is your job)—./cpp.mdPOSIX.1-2024 system (shell command processor), pubs.opengroup.org; man-pages 6.19 system(3), man7.orgPOSIX.1-2024; Linuxdocumented
PL-CPP-057§5.3 std::getenv threading: borrowed pointer the program must not modify, “may be overwritten by a subsequent call” (C17 §7.22.4.6 semantics adopted); concurrent getenv is race-free provided nothing modifies the environment; concurrent getenv+setenv/putenv is a data race you own — serialize mutation or restrict it to pre-thread startup; POSIX setenv copies, putenv aliases the caller’s string—./cpp.mdC++23 DIS N4950 [support.runtime], open-std.org; POSIX.1-2024 setenv/putenv, pubs.opengroup.orgC++98+ / libstdc++; POSIX.1-2024documented
PL-CPP-058§5.4 popen/pclose (<cstdio>) and the POSIX process trio: shell runs command with one pipe end wired to the child ("r": read child’s stdout; "w": write child’s stdin); pclose waits and returns the wait status (same decoding as system) or -1/ECHILD if already reaped; glibc mode 'e' (since glibc 2.9) sets FD_CLOEXEC; shell-in-the-loop ⇒ injection analysis of §5.2 applies; POSIX trio signatures (fork/execv/waitpid/waitid/posix_spawn) repeated as this chapter’s OS inventoryFILE* popen(const char* command, const char* mode); / int pclose(FILE* stream);./cpp.mdPOSIX.1-2024 popen/pclose, fork, exec, waitpid, waitid, posix_spawn, pubs.opengroup.org; man-pages 6.19 popen(3), man7.orgPOSIX.1-2024 (via <cstdio>); Linux (mode 'e' since glibc 2.9)documented
PL-CPP-059§5.5 fork from C++ — the multithreaded hazard: the child holds “a replica of the calling thread and its entire address space, possibly including the states of mutexes” and may only execute async-signal-safe operations until exec succeeds; every mutex locked by a vanished thread stays locked in the child — including the allocator’s internal lock, so new, malloc, std::string construction, anything that might allocate can deadlock immediately; child code between fork and exec must be C-style (AS-safe calls, then exec or _exit); no unwinding across exec (image overwritten); mitigations in order: posix_spawn, fork+AS-safe discipline, vfork-style paths via posix_spawn internals—./cpp.mdPOSIX.1-2024 fork (multithreaded-fork AS-safe rule), pubs.opengroup.org; man-pages 6.19 posix_spawn(3) (glibc vfork/clone internals), man7.orgPOSIX.1-2024; Linux (glibc 2.44)documented
PL-CPP-060§5.6 Exec-failure propagation, shape 1 — status convention: a failed exec returns -1/errno in the child, which must not run the parent’s cleanup; child _exit(127) (command not found / exec failed) or _exit(126) (found, not executable) — the same values POSIX assigns the shell and system, making parent-side decoding uniform; caveat: 127 is conventional, so a parent cannot distinguish “exec failed” from “exited 127” without a side-channel—./cpp.mdPOSIX.1-2024 exec, system (shell status conventions), pubs.opengroup.org; man-pages 6.19 wait(2) WEXITSTATUS, man7.orgPOSIX.1-2024; Linuxdocumented
PL-CPP-061§5.6 Exec-failure propagation, shape 2 — side-channel byte (CLOEXEC pipe): pipe2(fds, O_CLOEXEC) ([Linux]) before fork; in the child the write end closes automatically once exec succeeds; parent reads: EOF = exec succeeded, one errno byte = exec failed (then child reaped as _exit(127)); POSIX-only alternative: plain pipe + explicit fcntl(F_SETFD, FD_CLOEXEC)—./cpp.mdPOSIX.1-2024 pipe, fcntl, pubs.opengroup.org; man-pages 6.19 pipe(2)/pipe2(2) (O_CLOEXEC), fcntl(2), man7.orgPOSIX.1-2024; Linux (pipe2/O_CLOEXEC)documented
PL-CPP-062§5.7 fd inheritance, CLOEXEC, and RAII ownership: children inherit every descriptor without FD_CLOEXEC across exec — the leak default; hygiene set: O_CLOEXEC/pipe2 at creation ([Linux]), fcntl(F_SETFD, FD_CLOEXEC) ([POSIX]), posix_spawn_file_actions_addclose/adddup2 for child-side shaping, close_range(2) (since Linux 5.9) to sweep; from C++ any RAII fd type must set O_CLOEXEC at creation (a constructor-then-fork window is where leaks happen); double close after fd-number reuse silently corrupts an unrelated stream—./cpp.mdPOSIX.1-2024 fcntl(F_SETFD), posix_spawn file_actions, pubs.opengroup.org; man-pages 6.19 pipe(2)/pipe2(2), fcntl(2), close_range(2) (since Linux 5.9), man7.orgPOSIX.1-2024; Linux 5.9+ (close_range)documented
PL-CPP-063§5.8 Zombies, SIGCHLD, and reaping from C++: a terminated-but-unwaited child is a zombie until reaped (finite PIDs); reaping in a SIGCHLD handler is constrained by the signal-safe list — waitpid/waitid are on the POSIX async-signal-safe list, so the classic while (waitpid(-1, &st, WNOHANG) > 0); body is safe, but no C++ library calls in that handler; the sigwait-thread pattern reaps in a normal thread with full C++ library freedom (block SIGCHLD process-wide, dedicated thread loops on sigwait); SIGCHLD to SIG_IGN/SA_NOCLDWAIT auto-reaps but then waitpid cannot retrieve status—./cpp.mdPOSIX.1-2024 waitpid, waitid, sigwait, sigaction (SA_NOCLDWAIT), pubs.opengroup.org; man-pages 6.19 signal-safety(7), man7.orgPOSIX.1-2024; Linuxdocumented
PL-CPP-064§5.9 Timeouts and cancellation: POSIX has no timed child-wait — poll waitpid(pid, &st, WNOHANG) on a timer tick (0 = still running); at deadline kill(pid, SIGTERM), grace period, SIGKILL, then reap — a killed child surfaces as WIFSIGNALED/WTERMSIG == SIGKILL (143/137 through the 128+N decoder), not as an error; EINTR discipline: waitpid/waitid/read on pipes fail with EINTR when a caught signal arrives — retry, do not treat as error (or SA_RESTART where restartable); cancel the waiting, not the child — unblock via pthread_kill/eventfd; std::stop_token fits the wait loop (check stop_requested() between WNOHANG polls)—./cpp.mdPOSIX.1-2024 waitpid (WNOHANG, EINTR), kill, sigaction (SA_RESTART), pthread_kill, pubs.opengroup.org; man-pages 6.19 wait(2), man7.orgPOSIX.1-2024; Linuxdocumented
PL-CPP-065§6.1 std::thread: uncaught exceptions end the process — “If the invocation of invoke terminates with an uncaught exception, terminate is invoked”; a thread function has no caller to catch anything, so throw inside a thread function is main-level fatal by default; wrap at the boundary — top-level thread functions catch and convert to exit codes/flags/queues—./cpp.mdC++23 DIS N4950 [thread.thread.constr], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-066§6.2 std::thread destruction — the joinable-terminate rule: a joinable thread object destroyed ⇒ std::terminate; design note: neither implicit join (unbounded block surprise) nor implicit detach (correctness surprise) is defensible, so the choice is required before the destructor; trigger (1.12) covers move-assignment too — overwriting a joinable thread kills the process; every std::thread is joined or detached on every path, or (C++20) use std::jthread—./cpp.mdC++23 DIS N4950 [thread.thread.destr], [except.terminate] (1.12), open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-067§6.3 join/detach and the error surface: join() blocks until the thread function completes then releases the state (joinable() == false afterwards); error conditions no_such_process (not valid), invalid_argument (not joinable), resource_unavailable_would_block; detach() releases without waiting — a detached thread outliving main races static destruction (the exit-time UB rules apply verbatim); neither may be called on the thread object representing the calling thread (resource_deadlock_would_occur for self-join)—./cpp.mdC++23 DIS N4950 [thread.threads], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-068§6.4 std::jthread + std::stop_token — cooperative cancellation as RAII: std::thread + a stop_source the thread function can observe + automatic join at destruction; protocol: canceller calls request_stop(), worker polls stop_token::stop_requested() (or registers a stop_callback for push-style wakeup), the destructor joins; composes with §5.9 — the token cancels the wait loop while the OS-level child needs the TERM→KILL ladder—./cpp.mdC++23 DIS N4950 [thread.jthread.class], [thread.stoptoken], open-std.orgC++20+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-069§6.4 std::jthread destructor — exact clause wording (auto-join + stop-request-on-destruction semantics; chapter records the class-level contract “automatically join” and the destructor effect request_stop() + join() in the §6.4 example, but the destructor paragraph itself was not transcribed)—./cpp.mdeel.is/c++draft [thread.jthread.class] — working draft generated 2026-08-23, class-level contract only (destructor paragraph not transcribed; en.cppreference.com Cloudflare-blocked at retrieval)C++20+ / libstdc++ (GCC 14.2.0)source-limited
PL-CPP-070§6.5 std::async and the blocking-future destructor: launch::async runs “as if in a new thread of execution represented by a thread object”; launch::deferred runs lazily in the thread that calls get/wait; no policy (or the OR of both) ⇒ the implementation chooses — ask future::wait_for(0s) == future_status::deferred before assuming a thread exists; the last future from an async launch::async call “shall block until the associated thread has completed, as if joined” — a forgotten auto f = std::async(...) is a synchronous block; the deferred case blocks in the destructor too, running the whole computation at scope exit—./cpp.mdC++23 DIS N4950 [futures.async], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-071§6.6 std::mutex and the RAII lock guards: lock() blocks and may throw system_error (resource_deadlock_would_occur might be observed — not guaranteed) — the one RAII constructor that can fail visibly; hold locks with lock_guard (strict scope), unique_lock (movable, condition-variable compatible), scoped_lock (multi-mutex, C++17); no poison-and-recover — a thread dying with a mutex held blocks every later lock() forever; terminate-deadlock: a terminate handler or atexit/destructor that locks a mutex held anywhere else hangs exit—./cpp.mdC++23 DIS N4950 [thread.mutex], [except.terminate], open-std.orgC++11+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-072§6.7 thread_local destruction order at thread exit and process exit: destroyed when the thread’s initial function returns or when that thread calls std::exit; the exiting thread’s thread_local destruction strongly happens before static destruction; a thread_local destructor may not call into a static object that a later-destroyed static might also touch (order defined only within the same thread’s exit sequence); handlers running on “some” thread must not touch thread_local state (signal-safe rule 2); off-limits in a quick_exit/at_quick_exit context (Note 6)—./cpp.mdC++23 DIS N4950 [basic.start.term] p2, [support.start.term] Note 6, [support.signal] p3 (rule 2), open-std.orgISO C++ (C++11+)documented
PL-CPP-073§6.8 Standard streams: cin/cout/cerr constructed before the body of main begins (via ios_base::Init) and “the objects are not destroyed during program execution”; including <iostream> behaves as if it defined an ios_base::Init instance with static storage duration; libstdc++’s Init destructor (running in the §3.5 static-destruction sequence at exit) flushes cout/cerr/clog — skipped by _Exit/quick_exit/abort/terminate paths, so flush explicitly before choosing those exits; sync_with_stdio(false) decouples C++ streams from C stdio buffering (changes the fork interaction below)—./cpp.mdC++23 DIS N4950 [iostream.objects.overview] (§31.7.1), open-std.org; GCC 14.2.0 libstdc++ Init destructor (implementation layer)C++98+ / libstdc++ (GCC 14.2.0)documented
PL-CPP-074§6.9 stdio buffering × fork — the double-output bug, C++ edition: fork copies the process image including unflushed stdio and iostream buffers; a child that later calls exit (or returns from main) flushes the inherited copy — output appears twice; the buffer may be std::cout’s (a filebuf); with sync_with_stdio(true) (default) both share the C stream so one flush covers both; discipline: flush everything before forking, use _exit on child error paths; buffering × redirection (ls | wc) applies to std::cout exactly as to stdout — full-buffered on a pipe/FILE, line-buffered on a terminal—./cpp.mdPOSIX.1-2024 fork, _exit, pubs.opengroup.org; mechanism cross-documented in ./c.md §6.4POSIX.1-2024; Linux (glibc/libstdc++)documented
PL-CPP-075§6.10 fd/handle ownership and the leak/deadlock inventory: fd ownership via RAII (destructor-closing type with O_CLOEXEC at creation; FILE* in a unique_ptr-with-fclose deleter); the descriptor number is shared state across fork — one owner per fd, close in exactly one place; leaks in scope: unjoined joinable std::threads (terminate at destruction), unreaped children (zombies), un-CLOEXEC’d fds crossing exec, dynamic storage never deleted (OS reclaims wholesale), detached threads racing static destruction; deadlocks in scope: locks held during fork, mutex held across std::terminate/abort teardown, atexit handler waiting on a dead thread’s lock, the async blocking-destructor against your latency budget—./cpp.mdC++23 DIS N4950 [basic.start.term] p6, open-std.org; man-pages 6.19 pipe(2)/pipe2(2), close_range(2), man7.orgISO C++ (C++11+); POSIX.1-2024; Linuxdocumented