3.3 KiB
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)
#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
#include <stdio.h> // printf, FILE
#include <stdlib.h> // malloc, free
#include <string.h> // strlen, strdup
#include <unistd.h> // 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:
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/sysctl.h>
#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:
“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
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:
char *user = get_user();
/* ... */
free(user);
No special cases.
No defensive if (ptr) checks.
Dynamic allocation patterns (CRITICAL)
Example:
char *buf = malloc(len);
snprintf(buf, len, "...", ...);
return buf;
This is idiomatic C
Rules being followed:
- Allocate exactly what you need
- Initialize before use
- Return ownership to the caller
- Caller must
free()
Contrast this with C++:
- No destructors
- No smart pointers
- No safety net
Why no global buffers?
You could have done:
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
/proc/cpuinfo
/proc/uptime
- Text parsing
- Line-by-line scanning
- Defensive string handling
macOS / BSD
sysctl()
- Structured kernel APIs
- Buffer-size negotiation
- Integer & struct-based data
Time handling (classic C pain point)
time_t now = time(NULL);
difftime(now, boottime.tv_sec);
Why this matters:
time_tmay not be an integer- You never subtract times directly
difftime()handles portability
The main() function: clean by design
char *user = get_user();
/* ... */
free(user);
Notice:
- No logic
- No parsing
- No platform checks
- No error handling clutter
All complexity lives outside main().