C Programming Best Practices

Writing code that works is only the first step. Writing code that is correct, readable, maintainable, and bug-free is the real goal. This topic covers industry-accepted best practices, the most common errors beginners make, and practical techniques for finding and fixing bugs in C programs.

C Programming Best Practices

1. Always Initialize Variables

Uninitialized local variables in C contain garbage values — whatever happened to be in that memory location before. Always assign an initial value before use.


// BAD — garbage value
int sum;
printf("%d\n", sum);  // unpredictable output

// GOOD — explicitly initialized
int sum = 0;
printf("%d\n", sum);  // 0

2. Use Meaningful Variable Names


// BAD — impossible to understand
int x, y, z;
float t;

// GOOD — self-documenting
int studentCount, totalMarks;
float averageScore;

3. Always Check Return Values of Functions

Functions like fopen(), malloc(), and scanf() can fail. Always check their return values.


// BAD — no error check
FILE *fp = fopen("data.txt", "r");
fgets(buffer, 100, fp);   // crashes if file doesn't exist!

// GOOD — with error check
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("Cannot open file");
    return 1;
}
fgets(buffer, 100, fp);

4. Always Free Dynamically Allocated Memory


int *arr = (int *) malloc(10 * sizeof(int));
if (arr == NULL) { printf("Allocation failed\n"); return 1; }

// ... use arr ...

free(arr);      // always free when done
arr = NULL;     // prevent dangling pointer

5. Use Constants Instead of Magic Numbers


// BAD — what does 3.14159 mean here?
float area = 3.14159 * r * r;

// GOOD — meaning is clear
#define PI 3.14159
float area = PI * r * r;

6. Write One Function, One Purpose

Each function should do exactly one thing. If a function is getting very long or doing many unrelated things, split it into smaller functions.


// BAD — one big function doing everything
void processStudents() {
    // read, validate, sort, calculate, print — all in one!
}

// GOOD — each function has one job
void readStudents();
void validateStudents();
void sortStudents();
void calcAverages();
void printReport();

7. Use Comments Wisely


// BAD comment — just repeats what the code says
i++;   // increment i

// GOOD comment — explains WHY, not WHAT
totalPages++;   // account for the cover page added by the printer driver

8. Validate User Input


int age;
printf("Enter age (1-120): ");
scanf("%d", &age);

// Always validate before using
if (age < 1 || age > 120) {
    printf("Invalid age entered.\n");
    return 1;
}

9. Avoid Global Variables Where Possible

Global variables can be modified from anywhere, making bugs hard to track. Pass data through function parameters and return values instead.

10. Compile with Warning Flags


gcc -Wall -Wextra -std=c99 program.c -o program
  • -Wall — enables most common warnings
  • -Wextra — enables additional warnings
  • -std=c99 — enforce C99 standard

Treat all warnings as errors and fix them. A warning is the compiler telling something is likely wrong.

Common Errors in C

1. Missing & in scanf()


int num;
scanf("%d", num);   // WRONG — missing & → undefined behavior
scanf("%d", &num);  // CORRECT

2. Using = Instead of == for Comparison


int x = 5;

if (x = 10)         // WRONG — assigns 10 to x, condition always true!
    printf("ten");

if (x == 10)        // CORRECT — compares x with 10
    printf("ten");

3. Off-by-One Error in Arrays


int arr[5] = {1, 2, 3, 4, 5};

for (int i = 0; i <= 5; i++)       // WRONG — i goes to 5, arr[5] is out of bounds!
    printf("%d ", arr[i]);

for (int i = 0; i < 5; i++)        // CORRECT — i goes 0 to 4
    printf("%d ", arr[i]);

4. String Not Null-Terminated


char name[5] = {'A', 'l', 'i', 'c', 'e'};  // WRONG — no '\0'!
printf("%s", name);   // reads garbage beyond 'e'

char name[6] = {'A', 'l', 'i', 'c', 'e', '\0'};  // CORRECT
// Or simply:
char name[] = "Alice";  // automatic '\0' added

5. Integer Division When Float Expected


int a = 5, b = 2;
float result = a / b;         // WRONG — integer division: 5/2 = 2, stored as 2.0
float result = (float)a / b;  // CORRECT — float division: 2.5

6. Forgetting break in switch


int n = 2;
switch (n) {
    case 1: printf("One\n");  // no break — falls through!
    case 2: printf("Two\n");  // this runs
    case 3: printf("Three\n"); // this also runs!
    default: printf("Other\n");
}
// Output: Two Three Other  ← unintended!

// Fix: add break after each case

7. Using a Pointer After free()


int *p = (int*) malloc(sizeof(int));
*p = 10;
free(p);
printf("%d", *p);  // WRONG — dangling pointer! p points to freed memory

// Fix:
free(p);
p = NULL;           // set to NULL immediately after freeing

8. Comparing Strings with ==


char s1[] = "hello", s2[] = "hello";

if (s1 == s2)              // WRONG — compares addresses, not content!
    printf("Equal");

if (strcmp(s1, s2) == 0)   // CORRECT — compares characters
    printf("Equal");

9. Infinite Loop Due to Missing Update


int i = 0;
while (i < 5) {
    printf("%d\n", i);
    // MISSING: i++ → loop runs forever!
}

// Correct:
while (i < 5) {
    printf("%d\n", i);
    i++;   // always update the loop variable
}

10. Buffer Overflow with scanf


char name[10];
scanf("%s", name);   // RISKY — user might type more than 10 characters

// SAFER — limit input length
scanf("%9s", name);  // reads at most 9 characters + null terminator
// OR
fgets(name, 10, stdin);

Debugging Techniques in C

1. printf Debugging

The simplest and most common debugging technique — add printf statements to print variable values and confirm program flow.


void calculate(int a, int b) {
    printf("[DEBUG] calculate called with a=%d, b=%d\n", a, b);  // trace input

    int result = a + b;
    printf("[DEBUG] result = %d\n", result);  // trace output

    printf("Final: %d\n", result);
}

Use a #define DEBUG flag to easily enable/disable debug output without deleting it:


#define DEBUG 1

#if DEBUG
    #define LOG(fmt, ...) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)
#else
    #define LOG(fmt, ...)  // expands to nothing
#endif

int main() {
    int x = 42;
    LOG("x = %d", x);   // only printed when DEBUG = 1
    return 0;
}

2. Using assert()

assert() from <assert.h> checks a condition at runtime. If the condition is false, the program terminates with an error message showing the file and line number.


#include <stdio.h>
#include <assert.h>

int divide(int a, int b) {
    assert(b != 0);   // program halts here if b is zero
    return a / b;
}

int main() {
    printf("%d\n", divide(10, 2));   // 5
    printf("%d\n", divide(10, 0));   // assertion failure!
    return 0;
}

3. Using GDB Debugger

GDB is a powerful command-line debugger for C programs. Compile with -g to include debug symbols:


gcc -g program.c -o program
gdb ./program

Common GDB commands:

CommandDescription
runStart the program
break mainSet breakpoint at main function
break 25Set breakpoint at line 25
nextExecute next line (step over functions)
stepStep into function calls
print xPrint value of variable x
backtraceShow function call stack
continueContinue until next breakpoint
quitExit GDB

4. Valgrind — Memory Error Detection

Valgrind detects memory leaks, invalid memory accesses, and use of uninitialized memory on Linux/macOS:


gcc -g program.c -o program
valgrind --leak-check=full ./program

C Coding Style — Quick Guidelines

ElementRecommended StyleExample
Variablessnake_case or camelCasestudent_count, totalMarks
Constants / MacrosALL_CAPSMAX_SIZE, PI
FunctionscamelCase or snake_casecalcAverage(), find_max()
StructsPascalCaseStudentRecord, LinkedNode
Indentation4 spaces or 1 tab consistently
BracesOpening brace on same line or next line — be consistent
Line LengthKeep lines under 80 characters

Summary

Good C programming goes beyond just making code work. Initializing variables, validating input, checking function returns, freeing memory, and compiling with warning flags are habits that prevent most bugs before they appear. Knowing common errors — wrong scanf usage, off-by-one in arrays, missing break in switch, dangling pointers — lets problems be recognized and fixed quickly. Debugging with printf statements, assert(), GDB, and Valgrind are practical skills for finding and resolving issues in real programs. Applying these practices consistently transforms a beginner into a reliable, professional-quality C programmer.

Leave a Comment

Your email address will not be published. Required fields are marked *