qfetch/qfetchC-notes.md

3.3 KiB
Raw Blame History

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 wont expose those functions
  • Youll 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 its 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:

  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:

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_t may 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().