πŸ‡―πŸ‡΅ ζ—₯本θͺž | πŸ‡ΊπŸ‡Έ English
Advertisement

Intermediate C Topics

For readers past the basics: const, enum, typedef, errno, variadic, conditional compilation.

1. const correctness

const expresses "I promise not to modify this" in the type system. It clarifies intent and catches mistakes at compile time.
int sum_array(const int *a, int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    // a[i] = 0; // compile error β€” read-only
    return s;
}

Where the const goes

const int *p β†’ *p is read-only, p can change
int * const p β†’ p is fixed, *p is writable
const int * const p β†’ neither
Trick: read around the * β€” left is what the target can do, right is what the pointer can do.
const int MAX = 100;      // type-safer than #define
const double PI = 3.14159265358979;
Always mark "read-only" arguments const in library APIs β€” it's a contract for callers.

2. enum

Groups related integer constants under names. Pairs well with switch and eliminates magic numbers.
// Bad: magic number
int state = 2;
if (state == 3) { /* what's 3? */ }

// Good: enum
enum Color { RED, GREEN, BLUE };      // 0, 1, 2
enum Color c = GREEN;
if (c == RED) { /* meaning is clear */ }

Explicit values

enum HttpStatus {
    HTTP_OK = 200,
    HTTP_NOT_FOUND = 404,
    HTTP_SERVER_ERROR = 500
};

With switch

enum State { STATE_IDLE, STATE_RUN, STATE_STOP };

void handle(enum State s) {
    switch (s) {
        case STATE_IDLE: printf("idle\n"); break;
        case STATE_RUN:  printf("run\n");  break;
        case STATE_STOP: printf("stop\n"); break;
    }
}
Compiler help: -Wswitch-enum warns when a switch doesn't cover all enum values.

3. typedef

Give a new name to an existing type. Makes code cleaner and more self-documenting.
// struct typedef
typedef struct {
    int x;
    int y;
} Point;

Point p = {3, 4};

// function pointer
typedef int (*CmpFn)(const void *, const void *);
CmpFn cmp;

// arrays too
typedef char Buffer[256];
Buffer name;                  // same as char name[256]

Common standard typedefs

typedefunderlyinguse
size_tunsigned longsizes & indices
ssize_tlongsigned sizes
int32_t / uint64_tint / unsigned longexplicit width
time_tlongUNIX time
FILEstruct ...file handle
Avoid hiding pointers behind typedef (typedef struct S *SPtr;) β€” readers then can't see which variables are pointers.

4. Error handling with errno

Many standard functions set the global errno on failure. perror or strerror converts the code to a human-readable string.
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void) {
    FILE *fp = fopen("no_such_file.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        fprintf(stderr, "error %d: %s\n", errno, strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}

Common errno values

ConstantMeaning
ENOENTNo such file or directory
EACCESPermission denied
ENOMEMOut of memory
EINVALInvalid argument
EAGAINResource temporarily unavailable
EINTRInterrupted by a signal
Rules:
β‘  Only look at errno after a function reports failure.
β‘‘ Other library calls can overwrite errno; save it to a local if you need to keep it.
β‘’ For functions like strtol, set errno = 0; before the call so you can distinguish success from overflow.

5. Variadic functions (va_list)

How to write functions like printf that take an unknown number of arguments. Uses <stdarg.h>.
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);
    }
    va_end(ap);
    return total;
}

int main(void) {
    printf("%d\n", sum(3, 10, 20, 30));  // 60
    printf("%d\n", sum(5, 1, 2, 3, 4, 5));  // 15
}
Important constraints:
β‘  There is no runtime way to know the types and count of variadic arguments. Both sides must agree (count as first arg, sentinel, format string like printf).
β‘‘ Pass the actual type to va_arg; a mismatch is undefined behavior.
β‘’ char/short are promoted to int β€” use va_arg(ap, int), not va_arg(ap, char).

Typical logging wrapper

void log_info(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    fprintf(stderr, "[INFO] ");
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, "\n");
    va_end(ap);
}

log_info("user %s logged in at %d", name, ts);

6. Conditional compilation (#ifdef / #if)

Switch code on or off at build time. Essential for cross-platform code and optional debug instrumentation.

#ifdef / #ifndef

#ifdef DEBUG
    printf("x = %d\n", x);
#endif

// built with 'gcc -DDEBUG' β†’ printf enabled
// otherwise removed

#if with numeric tests

#define VERSION 3

#if VERSION >= 2
    // v2+ features
#else
    // v1 fallback
#endif

Platform branching

#if defined(_WIN32)
    #include <windows.h>
    Sleep(1000);
#elif defined(__APPLE__) || defined(__linux__)
    #include <unistd.h>
    sleep(1);
#else
    #error "unsupported platform"
#endif

Built-in macros

MacroMeaning
__FILE__current file
__LINE__current line
__func__function name (C99+)
__DATE__ / __TIME__build date/time
__STDC_VERSION__C standard version
// Handy debug macro
#define LOG(...) \
    fprintf(stderr, "[%s:%d %s] ", __FILE__, __LINE__, __func__), \
    fprintf(stderr, __VA_ARGS__), \
    fprintf(stderr, "\n")

LOG("x = %d", x);
// output: [main.c:42 main] x = 10
#pragma once: a compact alternative to the standard #ifndef FOO_H / #define / #endif include guard. Widely supported but non-standard.