C Notes
Learn essential C programming best practices covering coding standards, memory safety, error handling, performance optimization, and writing maintainable C code.
Writing C that works is one thing. Writing C that's maintainable, safe, portable, and performant is an entirely different challenge. Since C gives you direct access to memory and hardware without safety nets, the difference between good and bad C code can mean the difference between a reliable system and catastrophic failure.
This guide covers proven best practices that professional C developers follow in production codebases.
Coding Standards and Style
Consistent Naming Conventions
Choose a convention and stick with it throughout the project:
// snake_case (Linux kernel style - most common in C)
int buffer_size;
void process_data(int *input_array, size_t array_length);
// Prefix for module namespacing
typedef struct http_request http_request_t;
int http_request_parse(http_request_t *req, const char *raw);
int http_response_send(int socket_fd, const char *body);
// Constants: UPPER_SNAKE_CASE
#define MAX_BUFFER_SIZE 4096
#define DEFAULT_TIMEOUT_MS 5000Function Design Principles
Header File Best Practices
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
#include <stddef.h> // for size_t
#ifdef __cplusplus
extern "C" {
#endif
// Opaque type - hide implementation details
typedef struct MyLib MyLib;
// Clear, consistent API
MyLib* mylib_create(const char *config_path);
int mylib_process(MyLib *lib, const void *data, size_t len);
void mylib_destroy(MyLib *lib);
#ifdef __cplusplus
}
#endif
#endif // MYLIB_HMemory Safety
Always Check Allocations
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Wrapper that never returns NULL (exits on failure)
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
fprintf(stderr, "Fatal: malloc(%zu) failed\n", size);
exit(EXIT_FAILURE);
}
return ptr;
}
// Safe string duplication
char* safe_strdup(const char *src) {
if (!src) return NULL;
size_t len = strlen(src) + 1;
char *dst = safe_malloc(len);
memcpy(dst, src, len);
return dst;
}Prevent Buffer Overflows
Free Memory Properly
// Pattern: Set pointer to NULL after free
void cleanup(char **ptr) {
if (ptr && *ptr) {
free(*ptr);
*ptr = NULL; // Prevent double-free and use-after-free
}
}
// Pattern: Cleanup function for complex structures
typedef struct {
char *name;
int *scores;
size_t score_count;
} Student;
void student_free(Student *s) {
if (!s) return;
free(s->name);
s->name = NULL;
free(s->scores);
s->scores = NULL;
s->score_count = 0;
}Error Handling Patterns
Return Codes Convention
Goto for Cleanup (Linux Kernel Pattern)
#include <stdio.h>
#include <stdlib.h>
int process_file(const char *input_path, const char *output_path) {
int ret = -1;
FILE *in = NULL;
FILE *out = NULL;
char *buffer = NULL;
in = fopen(input_path, "r");
if (!in) goto cleanup;
out = fopen(output_path, "w");
if (!out) goto cleanup;
buffer = malloc(4096);
if (!buffer) goto cleanup;
// ... process data ...
ret = 0; // Success
cleanup:
free(buffer);
if (out) fclose(out);
if (in) fclose(in);
return ret;
}Performance Best Practices
Choose Right Data Types
Cache-Friendly Code
Avoid Unnecessary Work
Defensive Programming Checklist
| Practice | Why |
|---|---|
| Validate all inputs | Prevent undefined behavior |
| Check malloc returns | Handle out-of-memory gracefully |
Use sizeof(var) not sizeof(type) | Survives type changes |
| Initialize all variables | Avoid undefined values |
Use const liberally | Prevents accidental modification |
| Limit scope of variables | Reduces bugs and improves readability |
| One exit point per function | Easier cleanup logic |
Use static for internal functions | Limits symbol visibility |
Portability Guidelines
Interview Questions on Best Practices
Q1: Why use static for functions that are only used in one file? It limits the function's visibility to that translation unit, preventing symbol conflicts during linking and allowing the compiler to inline or optimize more aggressively.
Q2: Why is goto acceptable for cleanup in C? C lacks destructors and exception handling. The goto-cleanup pattern provides a single cleanup point, avoids deeply nested if-else chains, and is the standard pattern in the Linux kernel.
**Q3: What's wrong with malloc(sizeof(int) * n) vs malloc(n * sizeof(int))?** If n is very large, n * sizeof(int) might overflow before being passed to malloc. Use calloc(n, sizeof(int)) which checks for overflow, or manually validate.
Q4: How do you prevent double-free bugs? Set pointers to NULL after freeing. Check for NULL before free (though free(NULL) is safe). Use wrapper functions that handle this automatically.
Q5: When should you use const in function parameters? Always use const for pointer parameters when the function doesn't modify the pointed-to data. This documents intent, catches bugs at compile time, and can enable optimizations.
Summary
Writing high-quality C code requires discipline and awareness. Follow consistent coding standards, always validate inputs and check allocations, use bounded string functions, handle errors with clear return codes, free resources deterministically, and leverage compiler warnings and sanitizers to catch mistakes early. These practices don't just prevent bugs – they make your code readable, maintainable, and trustworthy in production environments.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for C Programming Best Practices – Coding Standards, Safety, and Performance.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this C Programming topic.
Search Terms
c-programming, c programming, programming, advanced, best, practices, c programming best practices – coding standards, safety, and performance
Related C Programming Topics