## The mindset shift: C vs C++ In C: - ❌ No classes, no RAII, no `std::string` - ✅ **You control memory explicitly** - ✅ Functions are small, focused, and composable - ✅ Portability comes from *conditional compilation* - “Each function returns ownership of dynamically allocated data, and the caller frees it.”* ## Feature macros (`_POSIX_C_SOURCE`) ```c #define _POSIX_C_SOURCE 200809L ``` ### Why this exists C standard libraries hide some functions unless you *opt in*. Without it: * Some systems won’t expose those functions * You’ll get mysterious warnings or missing symbols --- ## Headers: what C teaches you here ```c #include // printf, FILE #include // malloc, free #include // strlen, strdup #include // POSIX functions ``` In C: * Headers are **contracts** * If it’s not included, the compiler assumes *nothing* **Rule of thumb:** > If you call a function, its header must be included — no exceptions. --- ## Conditional compilation (`#if defined(...)`) Example: ```c #if defined(__APPLE__) || defined(__FreeBSD__) #include #endif ``` ### What this teaches you * C has **no runtime reflection** * Portability happens at **compile time** * The preprocessor literally removes code before compilation Think of it as: ```text “Only compile this code if the OS supports it.” ``` This is how *real* portable C software works (git, curl, openssh). --- ## The most important function in the file ```c static char *dup_or_unknown(const char *s) ``` ### Why this exists This function enforces a **contract**: * Every getter: * returns a valid `char *` * never returns `NULL` * always returns heap memory ### Why that matters It lets `main()` be *simple*: ```c char *user = get_user(); /* ... */ free(user); ``` No special cases. No defensive `if (ptr)` checks. --- ## Dynamic allocation patterns (CRITICAL) Example: ```c char *buf = malloc(len); snprintf(buf, len, "...", ...); return buf; ``` ### This is idiomatic C Rules being followed: 1. Allocate **exactly what you need** 2. Initialize before use 3. Return ownership to the caller 4. Caller must `free()` Contrast this with C++: * No destructors * No smart pointers * No safety net --- ## Why no global buffers? You *could* have done: ```c static char buf[256]; ``` But that would: * Break thread safety * Break reentrancy * Make functions non-composable Dynamic allocation makes your functions: * Reusable * Testable * Library-quality --- ## Reading system information in C Each platform teaches a lesson: ### Linux ```c /proc/cpuinfo /proc/uptime ``` * Text parsing * Line-by-line scanning * Defensive string handling ### macOS / BSD ```c sysctl() ``` * Structured kernel APIs * Buffer-size negotiation * Integer & struct-based data --- ## Time handling (classic C pain point) ```c time_t now = time(NULL); difftime(now, boottime.tv_sec); ``` Why this matters: * `time_t` may not be an integer * You *never* subtract times directly * `difftime()` handles portability --- ## The `main()` function: clean by design ```c char *user = get_user(); /* ... */ free(user); ``` Notice: * No logic * No parsing * No platform checks * No error handling clutter All complexity lives *outside* `main()`.