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.
intsum_array(constint*a, int n) {
int s = 0;
for (int i = 0; i < n; i++) s += a[i];
// a[i] = 0; // compile error β read-onlyreturn 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.
constint MAX = 100; // type-safer than #defineconstdouble 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 numberint state = 2;
if (state == 3) { /* what's 3? */ }
// Good: enumenum Color { RED, GREEN, BLUE }; // 0, 1, 2enum Color c = GREEN;
if (c == RED) { /* meaning is clear */ }
enum State { STATE_IDLE, STATE_RUN, STATE_STOP };
voidhandle(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 typedeftypedefstruct {
int x;
int y;
} Point;
Point p = {3, 4};
// function pointertypedefint (*CmpFn)(constvoid*, constvoid*);
CmpFn cmp;
// arrays tootypedefchar Buffer[256];
Buffer name; // same as char name[256]
Common standard typedefs
typedef
underlying
use
size_t
unsigned long
sizes & indices
ssize_t
long
signed sizes
int32_t / uint64_t
int / unsigned long
explicit width
time_t
long
UNIX time
FILE
struct ...
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.
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>intsum(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;
}
intmain(void) {
printf("%d\n", sum(3, 10, 20, 30)); // 60printf("%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).