Just like with real gun safety, not shooting your own foot in C first and foremost revolves around not ever pointing the gun at your foot
to start with, even if you think it's not loaded. The way you do this, like with a real gun, is by having habits and procedures for
handling it in different circumstances.
C, being a powerful tool, has no guardrails by default, but this doesn't mean you can't build some in places that make sense.
Macros are especially useful here as you can create what appear to be generic functions that are still typesafe or operate with type
info.
Allocation
A common mistake is allocating the wrong amount of memory for a struct:
Foo* x = malloc(sizeof(Foo));
This doesn't appear to be an error, but it is. You have to keep the type name inside
sizeof() in sync with the type of the
pointer being allocated for. It's pointing a gun at your foot even in this simple case, but it's then playing with the trigger
when the pointer is inside a struct:
stuff->x = malloc(sizeof(Foo));
Always use the pointer itself to determine the allocation size:
Foo* x = malloc(sizeof(*x));
But this still allows for another error, which is forgetting or typoing the dereference:
Foo* x = malloc(sizeof(x)); // derp, just the size of the pointer itself
We can fix this with a macro:
#define pmalloc(x) malloc(sizeof(*(x)))
Foo* x = pmalloc(x);
stuff->x = pmalloc(stuff->x);
There's still two places with the pointer itself though. An internal assignment can fix that, with a statement expression block to smooth some edge cases:
#define pmalloc(x) ({ x = malloc(sizeof(*(x))); })
Foo* x = pmalloc(x);
pmalloc(stuff->x);
We can take it a step further too, by adding support for arrays with an optional second argument. Then you can't screw up the math on accident.
#define pmalloc(...) pmalloc_N(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define pmalloc_N(n, ...) CAT(pmalloc_, n)(__VA_ARGS__)
#define pmalloc_1(p) ({ p = malloc(sizeof(*(p))); })
#define pmalloc_2(p, cnt) ({ p = malloc(sizeof(*(p) * (cnt)); })
#define pmalloc_3(p, xdim, ydim) ({ p = malloc(sizeof(*(p) * (xdim) * (ydim)); })
#define pmalloc_4(p, xdim, ydim, zdim) ({ p = malloc(sizeof(*(p) * (xdim) * (ydim) * (zdim)); })
If this macro doesn't immediately make complete sense to you, read through
Macro Basics.
Better yet, let's make a zero-initialized version because that't what we're going to do immediately most of the time anyway:
#define pcalloc(...) pcalloc_N(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define pcalloc_N(n, __VA_ARGS__) CAT(pcalloc_, n)(__VA_ARGS__)
#define pcalloc_1(p) ({ p = calloc(1, sizeof(*(p)); })
#define pcalloc_2(p, cnt) ({ p = calloc(1, sizeof(*(p) * (cnt)); })
#define pcalloc_3(p, xdim, ydim) ({ p = calloc(1, sizeof(*(p) * (xdim) * (ydim)); })
#define pcalloc_4(p, xdim, ydim, zdim) ({ p = calloc(1, sizeof(*(p) * (xdim) * (ydim) * (zdim)); })
With this one macro we have eliminated several entire categories of errors while also making the code smaller, neater, and easier to read without
any loss of clarity. Even if you didn't know what the pcalloc() function/macro was, you would be able to infer its operation immediately and
without any gotchas.
Compile Flags
C compilers are extremely flexible in what code they will accept and with what warnings. The default compile flags are trash and let you do all sorts
of really dumb things without raising an error. On the flip side
-Wpedantic wastes your time on all sorts of completely irrelevant trivia
and makes it very difficult to actually develop anything under such OCD psychosis.
Here is a good set of flags for GCC. Google them if you care about the details.
"-Wall",
"-Werror",
"-Wextra",
"-Wno-unused-result",
"-Wno-unused-variable",
"-Wno-unused-but-set-variable",
"-Wno-unused-function",
"-Wno-unused-label",
"-Wno-unused-parameter",
"-Wno-pointer-sign",
"-Wno-missing-braces",
"-Wno-maybe-uninitialized",
"-Wno-implicit-fallthrough",
"-Wno-sign-compare",
"-Wno-char-subscripts",
"-Wno-int-conversion",
"-Wno-int-to-pointer-cast",
"-Wno-enum-conversion",
"-Wno-unknown-pragmas",
"-Wno-sequence-point",
"-Wno-switch",
"-Wno-parentheses",
"-Wno-comment",
"-Wno-strict-aliasing",
"-Wno-endif-labels",
"-Wno-address-of-packed-member",
"-Wno-multichar",
"-Wno-type-limits", // 'comparison of unsigned expression in "< 0" is always false' bullshit
"-Wno-address-of-packed-member", // not useful in practice on x86-64
"-Werror=implicit-function-declaration",
"-Werror=uninitialized",
"-Werror=return-type",
"-Werror=incompatible-pointer-types"