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.
Always check the return value of
malloc(),calloc(),realloc(),fopen(), andfscanf()before trusting the result -- every one of these can fail, and using their result after a failure is undefined behaviorCompile with
-Wall -Wextra(and ideally-Wpedantic) from day one -- see the Compiler Warnings page for why this matters so much in CInitialize every variable at the point you declare it, even to a "placeholder" value -- reading an uninitialized variable is undefined behavior, not a guaranteed zero
Prefer
fgets()overgets()(removed from the standard entirely) or uncheckedscanf("%s", ...)--fgets()takes a buffer size and cannot overflow itMatch every
malloc/calloc/reallocwith exactly onefree()-- track ownership carefully so memory is neither leaked nor freed twiceUse
conston any pointer parameter or variable the function does not need to modify -- it documents intent and lets the compiler catch accidental writesAvoid magic numbers -- give meaningful names to constants with
#defineorconst/enuminstead of scattering literal numbers through the codeKeep functions short and focused on one task -- a function that does one thing is easier to test, name accurately, and reuse
Use include guards (
#ifndef/#define/#endif) or the near-universal#pragma oncein every header file, to prevent double-inclusion errorsPrefer
snprintf()oversprintf()--snprintf()takes a buffer size and cannot write past the end of your buffer, whilesprintf()has no such protectionCheck array bounds explicitly wherever an index comes from user input, a loop counter, or arithmetic that could go out of range
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
#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;
}