CBest Practices

Best Practices

C gives you very little safety net by default — no bounds checking, no garbage collector, no mandatory error handling. Writing reliable C is less about clever tricks and more about consistently following a short list of disciplined habits. None of these are exotic; together they eliminate the large majority of bugs that plague careless C code.

  1. Always check the return value of malloc(), calloc(), realloc(), fopen(), and fscanf() before trusting the result -- every one of these can fail, and using their result after a failure is undefined behavior

  2. Compile with -Wall -Wextra (and ideally -Wpedantic) from day one -- see the Compiler Warnings page for why this matters so much in C

  3. Initialize every variable at the point you declare it, even to a "placeholder" value -- reading an uninitialized variable is undefined behavior, not a guaranteed zero

  4. Prefer fgets() over gets() (removed from the standard entirely) or unchecked scanf("%s", ...) -- fgets() takes a buffer size and cannot overflow it

  5. Match every malloc/calloc/realloc with exactly one free() -- track ownership carefully so memory is neither leaked nor freed twice

  6. Use const on any pointer parameter or variable the function does not need to modify -- it documents intent and lets the compiler catch accidental writes

  7. Avoid magic numbers -- give meaningful names to constants with #define or const/enum instead of scattering literal numbers through the code

  8. Keep functions short and focused on one task -- a function that does one thing is easier to test, name accurately, and reuse

  9. Use include guards (#ifndef/#define/#endif) or the near-universal #pragma once in every header file, to prevent double-inclusion errors

  10. Prefer snprintf() over sprintf() -- snprintf() takes a buffer size and cannot write past the end of your buffer, while sprintf() has no such protection

  11. Check array bounds explicitly wherever an index comes from user input, a loop counter, or arithmetic that could go out of range

  12. Free resources (memory, file handles) in the reverse order you acquired them, and as close as possible to the point where they are no longer needed

Build these habits into muscle memory, not a checklist you consult later
Every item above is cheap to do while writing new code and expensive to retrofit onto code that already shipped. Pair them with the compiler warnings and sanitizers covered elsewhere in this section -- the habits catch mistakes at the design stage, the tools catch what slips through.

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NAME_LEN 64

// A small example applying several of the practices above at once.
int main(void) {
    char name[MAX_NAME_LEN] = {0};   // initialized, bounded buffer

    printf("Enter your name: ");
    if (fgets(name, sizeof(name), stdin) == NULL) {  // checked, bounded read
        fprintf(stderr, "Failed to read input\n");
        return EXIT_FAILURE;
    }
    name[strcspn(name, "\n")] = '\0'; // strip trailing newline safely

    char *greeting = malloc(MAX_NAME_LEN + 16);
    if (greeting == NULL) {          // checked allocation
        fprintf(stderr, "Out of memory\n");
        return EXIT_FAILURE;
    }

    snprintf(greeting, MAX_NAME_LEN + 16, "Hello, %s!", name); // bounded write

    printf("%s\n", greeting);
    free(greeting);                  // matching free
    return EXIT_SUCCESS;
}