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:
| Command | Description |
|---|---|
| run | Start the program |
| break main | Set breakpoint at main function |
| break 25 | Set breakpoint at line 25 |
| next | Execute next line (step over functions) |
| step | Step into function calls |
| print x | Print value of variable x |
| backtrace | Show function call stack |
| continue | Continue until next breakpoint |
| quit | Exit 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
| Element | Recommended Style | Example |
|---|---|---|
| Variables | snake_case or camelCase | student_count, totalMarks |
| Constants / Macros | ALL_CAPS | MAX_SIZE, PI |
| Functions | camelCase or snake_case | calcAverage(), find_max() |
| Structs | PascalCase | StudentRecord, LinkedNode |
| Indentation | 4 spaces or 1 tab consistently | — |
| Braces | Opening brace on same line or next line — be consistent | — |
| Line Length | Keep 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.
