C Best Practices Guide ✨
🎯 Overview
This guide summarizes best practices for C programming, helping you write high-quality, maintainable, and efficient C code. Whether you're a beginner or an experienced developer, following these practices will significantly improve your code quality.
📋 Table of Contents
- Code Style and Formatting
- Naming Conventions
- Function Design Principles
- Memory Management
- Error Handling
- Header File Management
- Performance Optimization
- Secure Programming
- Debugging Techniques
- Code Review Checklist
1️⃣ Code Style and Formatting
Indentation and Spacing
c
// ✅ Recommended: Use 4 spaces for indentation
if (condition) {
printf("Hello World\n");
if (another_condition) {
do_something();
}
}
// ✅ Add spaces around operators
int result = a + b * c;
int sum = (x + y) * z;
// ❌ Avoid: Inconsistent indentation
if (condition) {
printf("Bad\n");
printf("Inconsistent\n");
}Brace Style
c
// ✅ K&R style (recommended)
if (condition) {
do_something();
} else {
do_other_thing();
}
// ✅ Allman style (also acceptable)
if (condition)
{
do_something();
}
else
{
do_other_thing();
}
// ⚠️ Use braces even for single-line statements
if (condition) {
return;
}
// ❌ Avoid: Error-prone style
if (condition)
statement1();
statement2(); // This line is NOT inside the if block!Line Length and Wrapping
c
// ✅ Keep line length within 80-120 characters
int result = calculate_something(parameter1, parameter2,
parameter3, parameter4);
// ✅ Wrap long strings
const char *message = "This is a very long string "
"that needs to be split into multiple "
"lines for better readability";
// ✅ Function parameter alignment
void long_function_name(int first_parameter,
int second_parameter,
int third_parameter) {
// Function body
}Empty Line Usage
c
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
// ✅ Add one empty line between functions
int function1(void) {
return 0;
}
int function2(void) {
return 1;
}
// ✅ Add empty line between logical blocks
int main(void) {
// Initialization section
int count = 0;
int sum = 0;
// Processing section
for (int i = 0; i < 10; i++) {
sum += i;
}
// Output section
printf("Sum: %d\n", sum);
return 0;
}Comment Standards
c
/**
* @brief Calculate the sum of two integers
* @param a The first integer
* @param b The second integer
* @return The sum of the two integers
*/
int add(int a, int b) {
return a + b;
}
// ✅ Single-line comments for brief explanations
int count = 0; // Counter
/* ✅ Multi-line comments for detailed explanations */
/*
* This function implements a complex algorithm
* including the following steps:
* 1. Initialize data structures
* 2. Process input
* 3. Return results
*/
// ❌ Avoid: Useless comments
int i = 0; // Set i to 0 (obvious)
// ❌ Avoid: Commented-out code (should be deleted)
// int old_code = 123;
// do_something_old();2️⃣ Naming Conventions
Variable Naming
c
// ✅ Use meaningful names
int student_count;
double average_score;
char *user_name;
// ✅ Use lowercase with underscores for local variables
int loop_counter;
double total_price;
// ✅ Use g_ prefix for global variables
int g_max_connections;
char *g_config_path;
// ❌ Avoid: Single-letter variables (except loop counters)
int a, b, c; // Unclear meaning
// ✅ Loop counters can use i, j, k
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// ...
}
}Constant Naming
c
// ✅ Use ALL_CAPS with underscores for macro constants
#define MAX_BUFFER_SIZE 1024
#define PI 3.14159265359
#define ERROR_CODE_INVALID -1
// ✅ Use ALL_CAPS for enum constants
enum Status {
STATUS_SUCCESS,
STATUS_FAILURE,
STATUS_PENDING
};
// ✅ const constants can use lowercase
const int max_retry_count = 3;
const double tax_rate = 0.08;Function Naming
c
// ✅ Use verbs at the beginning to describe functionality
int calculate_sum(int *array, int size);
void print_result(int value);
bool is_valid_input(const char *input);
char *get_user_name(void);
void set_config_value(const char *key, const char *value);
// ✅ Boolean functions use is_, has_, can_ prefixes
bool is_empty(const char *str);
bool has_permission(int user_id);
bool can_access(const char *resource);
// ❌ Avoid: Unclear names
void process(void);
int do_it(int x);Type Naming
c
// ✅ Use _t suffix or capitalized first letter for structs
typedef struct {
int x;
int y;
} point_t;
typedef struct Student {
char name[50];
int age;
double score;
} Student;
// ✅ Enum types
typedef enum {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
} color_t;
// ✅ Function pointer types
typedef int (*compare_func_t)(const void *, const void *);3️⃣ Function Design Principles
Single Responsibility Principle
c
// ✅ Good: Each function does one thing
int read_file(const char *filename, char *buffer, size_t size) {
// Only responsible for reading files
}
int parse_data(const char *data) {
// Only responsible for parsing data
}
void display_result(int result) {
// Only responsible for displaying results
}
// ❌ Bad: One function does too much
int read_parse_and_display(const char *filename) {
// Reading, parsing, and displaying all in one function
}Function Length
c
// ✅ Keep functions short (recommended: no more than 50 lines)
int calculate_average(int *scores, int count) {
if (count == 0) {
return 0;
}
int sum = 0;
for (int i = 0; i < count; i++) {
sum += scores[i];
}
return sum / count;
}
// ✅ Split complex logic into smaller functions
int process_data(const char *input) {
if (!validate_input(input)) {
return -1;
}
char *cleaned = clean_data(input);
int result = transform_data(cleaned);
free(cleaned);
return result;
}Parameter Count
c
// ✅ No more than 5 parameters
int create_user(const char *name, int age, const char *email);
// ✅ Use struct when too many parameters
typedef struct {
const char *name;
int age;
const char *email;
const char *phone;
const char *address;
} user_info_t;
int create_user_ex(const user_info_t *info);
// ❌ Avoid: Too many parameters
int create_user_bad(const char *name, int age, const char *email,
const char *phone, const char *address,
int status, double balance);Return Value Design
c
// ✅ Use return value to indicate success/failure
int open_file(const char *filename, FILE **fp) {
*fp = fopen(filename, "r");
if (*fp == NULL) {
return -1; // Failure
}
return 0; // Success
}
// ✅ Use enum to represent multiple states
typedef enum {
RESULT_SUCCESS = 0,
RESULT_ERROR_FILE_NOT_FOUND = -1,
RESULT_ERROR_PERMISSION_DENIED = -2,
RESULT_ERROR_INVALID_FORMAT = -3
} result_code_t;
result_code_t load_config(const char *filename);
// ✅ Use output parameters to return multiple values
int get_min_max(int *array, int size, int *min, int *max) {
if (array == NULL || size <= 0) {
return -1;
}
*min = *max = array[0];
for (int i = 1; i < size; i++) {
if (array[i] < *min) *min = array[i];
if (array[i] > *max) *max = array[i];
}
return 0;
}4️⃣ Memory Management
Dynamic Memory Allocation
c
// ✅ Check malloc return value
int *array = (int *)malloc(size * sizeof(int));
if (array == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return -1;
}
// ✅ Use calloc to initialize to zero
int *zeros = (int *)calloc(count, sizeof(int));
if (zeros == NULL) {
perror("calloc");
return -1;
}
// ✅ Use sizeof to calculate size
person_t *person = (person_t *)malloc(sizeof(person_t));
// or
person_t *person = (person_t *)malloc(sizeof(*person));
// ❌ Avoid: Hard-coded sizes
person_t *person = (person_t *)malloc(100); // BadMemory Deallocation
c
// ✅ Set pointer to NULL after freeing
void cleanup(char **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL; // Prevent dangling pointer
}
}
// ✅ Pair allocation and deallocation
void process_data(void) {
char *buffer = (char *)malloc(BUFFER_SIZE);
if (buffer == NULL) {
return;
}
// Use buffer
free(buffer); // Ensure to free
}
// ✅ Use goto for unified resource cleanup
int complex_function(void) {
char *buf1 = NULL;
char *buf2 = NULL;
FILE *fp = NULL;
int result = -1;
buf1 = (char *)malloc(SIZE1);
if (buf1 == NULL) goto cleanup;
buf2 = (char *)malloc(SIZE2);
if (buf2 == NULL) goto cleanup;
fp = fopen("file.txt", "r");
if (fp == NULL) goto cleanup;
// Processing logic
result = 0;
cleanup:
free(buf1);
free(buf2);
if (fp != NULL) fclose(fp);
return result;
}Avoiding Memory Leaks
c
// ❌ Memory leak example
void leak_example(void) {
char *data = (char *)malloc(100);
if (some_condition) {
return; // Forgot to free data!
}
free(data);
}
// ✅ Fix: Ensure freeing on all paths
void fixed_example(void) {
char *data = (char *)malloc(100);
if (data == NULL) {
return;
}
if (some_condition) {
free(data);
return;
}
free(data);
}
// ✅ Better: Use unified exit point
void better_example(void) {
char *data = (char *)malloc(100);
if (data == NULL) {
return;
}
int should_exit = 0;
if (some_condition) {
should_exit = 1;
}
// Other processing
free(data);
if (should_exit) {
return;
}
}Array Bounds Checking
c
// ✅ Always check array bounds
void safe_array_access(int *array, int size, int index) {
if (array == NULL) {
fprintf(stderr, "Array is NULL\n");
return;
}
if (index < 0 || index >= size) {
fprintf(stderr, "Index out of bounds: %d\n", index);
return;
}
array[index] = 42;
}
// ✅ Use strncpy instead of strcpy
void safe_string_copy(char *dest, const char *src, size_t dest_size) {
if (dest == NULL || src == NULL || dest_size == 0) {
return;
}
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0'; // Ensure null termination
}5️⃣ Error Handling
Return Error Codes
c
// ✅ Define clear error codes
typedef enum {
ERR_SUCCESS = 0,
ERR_INVALID_PARAM = -1,
ERR_OUT_OF_MEMORY = -2,
ERR_FILE_NOT_FOUND = -3,
ERR_PERMISSION_DENIED = -4
} error_code_t;
// ✅ Functions return error codes
error_code_t read_config(const char *filename, config_t *config) {
if (filename == NULL || config == NULL) {
return ERR_INVALID_PARAM;
}
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return ERR_FILE_NOT_FOUND;
}
// Read configuration
fclose(fp);
return ERR_SUCCESS;
}
// ✅ Check error codes when calling
int main(void) {
config_t config;
error_code_t err = read_config("config.txt", &config);
if (err != ERR_SUCCESS) {
fprintf(stderr, "Failed to read config: %d\n", err);
return 1;
}
// Use configuration
return 0;
}Using errno
c
#include <errno.h>
#include <string.h>
// ✅ Check errno for system calls
int safe_file_operation(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "Cannot open file '%s': %s\n",
filename, strerror(errno));
return -1;
}
// File operations
if (fclose(fp) != 0) {
fprintf(stderr, "Failed to close file: %s\n", strerror(errno));
return -1;
}
return 0;
}Using Assertions
c
#include <assert.h>
// ✅ Use assertions to check for conditions that should never happen
void process_array(int *array, int size) {
assert(array != NULL); // Check during debugging
assert(size > 0);
for (int i = 0; i < size; i++) {
array[i] *= 2;
}
}
// ⚠️ Note: Assertions are removed in Release builds
// For conditions that must be checked, use if statements
void critical_check(int *array, int size) {
if (array == NULL || size <= 0) {
fprintf(stderr, "Invalid parameters\n");
exit(EXIT_FAILURE);
}
// Processing
}Error Logging
c
#include <time.h>
// ✅ Implement a simple logging system
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
} log_level_t;
void log_message(log_level_t level, const char *format, ...) {
const char *level_str[] = {"DEBUG", "INFO", "WARNING", "ERROR"};
time_t now = time(NULL);
char time_buf[26];
ctime_r(&now, time_buf);
time_buf[24] = '\0'; // Remove newline
fprintf(stderr, "[%s] [%s] ", time_buf, level_str[level]);
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, "\n");
}
// Usage example
int main(void) {
log_message(LOG_INFO, "Program started");
log_message(LOG_ERROR, "Cannot open file: %s", "config.txt");
return 0;
}6️⃣ Header File Management
Header Guards
c
// ✅ Use header guard macros
// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
// Declarations and definitions
#endif /* MYHEADER_H */
// ✅ Or use #pragma once (non-standard but widely supported)
// myheader.h
#pragma once
// Declarations and definitionsHeader File Organization
c
// ✅ Recommended header file structure
// mymodule.h
#ifndef MYMODULE_H
#define MYMODULE_H
// 1. System header files
#include <stdio.h>
#include <stdlib.h>
// 2. Third-party library header files
#include <sqlite3.h>
// 3. Project internal header files
#include "common.h"
#include "utils.h"
// 4. Macro definitions
#define MAX_SIZE 100
// 5. Type definitions
typedef struct {
int id;
char name[50];
} record_t;
// 6. Function declarations
int init_module(void);
void cleanup_module(void);
int process_record(const record_t *record);
#endif /* MYMODULE_H */Avoiding Circular Dependencies
c
// ✅ Use forward declarations
// a.h
#ifndef A_H
#define A_H
struct B; // Forward declaration
typedef struct A {
int value;
struct B *b_ptr;
} A;
void process_a(A *a);
#endif
// b.h
#ifndef B_H
#define B_H
struct A; // Forward declaration
typedef struct B {
int value;
struct A *a_ptr;
} B;
void process_b(B *b);
#endifMinimizing Header Dependencies
c
// ✅ Include only necessary headers in .h files
// mymodule.h
#ifndef MYMODULE_H
#define MYMODULE_H
// Only include headers needed for the interface
#include <stddef.h> // For size_t
typedef struct mydata mydata_t; // Opaque type
mydata_t *create_data(size_t size);
void destroy_data(mydata_t *data);
#endif
// ✅ Include implementation headers in .c files
// mymodule.c
#include "mymodule.h"
#include <stdlib.h>
#include <string.h>
struct mydata {
size_t size;
char *buffer;
};
mydata_t *create_data(size_t size) {
mydata_t *data = malloc(sizeof(mydata_t));
if (data != NULL) {
data->size = size;
data->buffer = malloc(size);
}
return data;
}7️⃣ Performance Optimization
Choosing the Right Data Structure
c
// ✅ Frequent lookups: Use hash tables
// ✅ Ordered data: Use binary search trees
// ✅ Fixed size: Use arrays
// ✅ Dynamic size: Use linked lists
// Example: Use array instead of linked list when size is known
typedef struct {
int *data;
size_t size;
size_t capacity;
} dynamic_array_t;
dynamic_array_t *create_array(size_t initial_capacity) {
dynamic_array_t *arr = malloc(sizeof(dynamic_array_t));
if (arr == NULL) return NULL;
arr->data = malloc(initial_capacity * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initial_capacity;
return arr;
}Avoiding Unnecessary Calculations
c
// ❌ Repeated calculations in loop
for (int i = 0; i < strlen(str); i++) { // strlen is called every time
process(str[i]);
}
// ✅ Move calculations outside loop
size_t len = strlen(str);
for (size_t i = 0; i < len; i++) {
process(str[i]);
}
// ❌ Repeated function calls
int result = expensive_function() + expensive_function();
// ✅ Cache result
int temp = expensive_function();
int result = temp + temp;Using const and restrict
c
// ✅ Use const to help compiler optimize
int sum_array(const int *array, size_t size) {
int sum = 0;
for (size_t i = 0; i < size; i++) {
sum += array[i];
}
return sum;
}
// ✅ Use restrict to indicate non-overlapping pointers (C99)
void copy_array(int * restrict dest, const int * restrict src, size_t n) {
for (size_t i = 0; i < n; i++) {
dest[i] = src[i];
}
}Inline Functions
c
// ✅ Use inline for simple, frequently called functions
static inline int max(int a, int b) {
return (a > b) ? a : b;
}
static inline int min(int a, int b) {
return (a < b) ? a : b;
}
// ⚠️ Don't inline large functions
// ❌ This function is too large to inline
static inline void complex_function(void) {
// 100 lines of code...
}Bit Manipulation Optimization
c
// ✅ Use bit operations instead of multiplication/division (for powers of 2)
int multiply_by_8(int x) {
return x << 3; // Faster than x * 8
}
int divide_by_4(int x) {
return x >> 2; // Faster than x / 4
}
// ✅ Check parity
bool is_even(int x) {
return (x & 1) == 0;
}
// ✅ Swap two variables (without temporary variable)
void swap_xor(int *a, int *b) {
if (a != b) { // Ensure not the same address
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}Cache-Friendly Code
c
// ✅ Access 2D arrays in row-major order (cache-friendly)
void process_matrix_row_major(int matrix[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
process(matrix[i][j]);
}
}
}
// ❌ Column-major access (cache-unfriendly)
void process_matrix_col_major(int matrix[][COLS], int rows) {
for (int j = 0; j < COLS; j++) {
for (int i = 0; i < rows; i++) {
process(matrix[i][j]); // Strided access
}
}
}
// ✅ Aligned data structures
typedef struct {
char c; // 1 byte
// 3 bytes padding
int i; // 4 bytes
double d; // 8 bytes
} aligned_struct_t; // Total 16 bytes
// ❌ Unaligned structure (wastes space)
typedef struct {
char c; // 1 byte
double d; // 7 bytes padding + 8 bytes
int i; // 4 bytes
// 4 bytes padding
} unaligned_struct_t; // Total 24 bytes8️⃣ Secure Programming
Input Validation
c
// ✅ Always validate user input
int read_integer(const char *prompt) {
char buffer[100];
int value;
printf("%s", prompt);
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
return -1;
}
// Check input format
if (sscanf(buffer, "%d", &value) != 1) {
fprintf(stderr, "Invalid integer input\n");
return -1;
}
return value;
}
// ✅ Validate file paths
bool is_safe_path(const char *path) {
if (path == NULL) return false;
// Check for path traversal attacks
if (strstr(path, "..") != NULL) {
return false;
}
// Check for absolute paths
if (path[0] == '/') {
return false;
}
return true;
}Buffer Overflow Protection
c
// ✅ Use safe string functions
void safe_string_operations(void) {
char dest[10];
const char *src = "This is a long string";
// Use strncpy instead of strcpy
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// Use snprintf instead of sprintf
char buffer[50];
snprintf(buffer, sizeof(buffer), "Value: %d", 42);
// Use strncat instead of strcat
char result[20] = "Hello";
strncat(result, " World", sizeof(result) - strlen(result) - 1);
}
// ✅ Check buffer sizes
int safe_copy(char *dest, size_t dest_size, const char *src) {
if (dest == NULL || src == NULL || dest_size == 0) {
return -1;
}
size_t src_len = strlen(src);
if (src_len >= dest_size) {
return -1; // Buffer too small
}
strcpy(dest, src);
return 0;
}Integer Overflow Checking
c
#include <limits.h>
// ✅ Check for addition overflow
bool safe_add(int a, int b, int *result) {
if (a > 0 && b > INT_MAX - a) {
return false; // Overflow
}
if (a < 0 && b < INT_MIN - a) {
return false; // Underflow
}
*result = a + b;
return true;
}
// ✅ Check for multiplication overflow
bool safe_multiply(int a, int b, int *result) {
if (a > 0 && b > 0 && a > INT_MAX / b) {
return false;
}
if (a > 0 && b < 0 && b < INT_MIN / a) {
return false;
}
if (a < 0 && b > 0 && a < INT_MIN / b) {
return false;
}
if (a < 0 && b < 0 && a < INT_MAX / b) {
return false;
}
*result = a * b;
return true;
}Format String Security
c
// ❌ Dangerous: User input as format string
void unsafe_print(const char *user_input) {
printf(user_input); // Format string vulnerability!
}
// ✅ Safe: Use fixed format string
void safe_print(const char *user_input) {
printf("%s", user_input);
}
// ✅ Safer: Limit output length
void safer_print(const char *user_input) {
printf("%.100s", user_input); // Maximum 100 characters
}9️⃣ Debugging Techniques
Using Debug Macros
c
// ✅ Define debug macros
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "[DEBUG] %s:%d:%s(): " fmt "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...) do {} while(0)
#endif
// Usage example
int main(void) {
int value = 42;
DEBUG_PRINT("value = %d", value);
return 0;
}Breakpoints and Conditional Breakpoints
c
// ✅ Using GDB for debugging
// Compile with -g option
// gcc -g -o program program.c
// GDB commands:
// break main # Set breakpoint at main function
// break file.c:10 # Set breakpoint at line 10
// break func if x > 5 # Conditional breakpoint
// run # Run program
// next # Step over (don't enter functions)
// step # Step into functions
// print variable # Print variable value
// watch variable # Watch variable changesMemory Debugging Tools
c
// ✅ Using Valgrind to detect memory issues
// valgrind --leak-check=full ./program
// Example: Detect memory leaks
void memory_leak_example(void) {
int *ptr = malloc(100 * sizeof(int));
// forgot free(ptr); // Valgrind will detect
}
// ✅ Using AddressSanitizer
// gcc -fsanitize=address -g -o program program.c
// ./program
// Example: Detect buffer overflows
void buffer_overflow_example(void) {
int array[10];
array[10] = 42; // AddressSanitizer will detect
}Logging and Tracing
c
// ✅ Implement function call tracing
#ifdef TRACE
static int trace_level = 0;
#define TRACE_ENTER() \
do { \
for (int i = 0; i < trace_level; i++) printf(" "); \
printf("-> %s\n", __func__); \
trace_level++; \
} while(0)
#define TRACE_EXIT() \
do { \
trace_level--; \
for (int i = 0; i < trace_level; i++) printf(" "); \
printf("<- %s\n", __func__); \
} while(0)
#else
#define TRACE_ENTER() do {} while(0)
#define TRACE_EXIT() do {} while(0)
#endif
void function_a(void) {
TRACE_ENTER();
// Function body
TRACE_EXIT();
}Static Analysis Tools
bash
# ✅ Using cppcheck for static analysis
cppcheck --enable=all --inconclusive program.c
# ✅ Using clang-tidy
clang-tidy program.c -- -I/usr/include
# ✅ Using compiler warnings
gcc -Wall -Wextra -Wpedantic -Werror program.c🔍 Code Review Checklist
Code Quality Checks
markdown
#### Readability
- [ ] Consistent code formatting (indentation, spaces, line breaks)
- [ ] Clear and meaningful variable and function names
- [ ] Comments explaining complex logic
- [ ] Reasonable function length (no more than 50 lines)
- [ ] Avoid deep nesting (no more than 3 levels)
#### Correctness
- [ ] All pointers checked for NULL before use
- [ ] Array access has bounds checking
- [ ] Integer operations consider overflow
- [ ] Floating-point comparisons use epsilon
- [ ] All function return values are checked
#### Memory Management
- [ ] Every malloc/calloc has a corresponding free
- [ ] Freed pointers set to NULL
- [ ] No memory leaks
- [ ] No double frees
- [ ] No use of freed memory
#### Error Handling
- [ ] All potentially failing operations have error handling
- [ ] Error messages are clear and helpful
- [ ] Resources are properly cleaned up on error paths
- [ ] Use appropriate error codes or return values
#### Security
- [ ] Input validation is complete
- [ ] Use safe string functions
- [ ] Prevent buffer overflows
- [ ] Prevent format string vulnerabilities
- [ ] Clear sensitive data after use
#### Performance
- [ ] Avoid unnecessary calculations
- [ ] Choose appropriate data structures
- [ ] Reasonable loop optimizations
- [ ] No obvious performance bottlenecks
#### Maintainability
- [ ] Good code modularization
- [ ] Single responsibility per function
- [ ] Avoid code duplication
- [ ] Reasonable header file organization
- [ ] Necessary documentation and comments📚 Practical Example: Complete Module Implementation
Example: Dynamic Array Implementation
c
// dynamic_array.h
#ifndef DYNAMIC_ARRAY_H
#define DYNAMIC_ARRAY_H
#include <stddef.h>
#include <stdbool.h>
typedef struct dynamic_array dynamic_array_t;
/**
* @brief Create a dynamic array
* @param initial_capacity Initial capacity
* @return Pointer to array on success, NULL on failure
*/
dynamic_array_t *da_create(size_t initial_capacity);
/**
* @brief Destroy a dynamic array
* @param arr Pointer to array
*/
void da_destroy(dynamic_array_t *arr);
/**
* @brief Add element to the end of array
* @param arr Pointer to array
* @param value Value to add
* @return true on success, false on failure
*/
bool da_push(dynamic_array_t *arr, int value);
/**
* @brief Remove element from the end of array
* @param arr Pointer to array
* @param value Output parameter, stores removed value
* @return true on success, false on failure
*/
bool da_pop(dynamic_array_t *arr, int *value);
/**
* @brief Get array size
* @param arr Pointer to array
* @return Number of elements in array
*/
size_t da_size(const dynamic_array_t *arr);
/**
* @brief Get element at specified index
* @param arr Pointer to array
* @param index Index
* @param value Output parameter, stores element value
* @return true on success, false on failure
*/
bool da_get(const dynamic_array_t *arr, size_t index, int *value);
/**
* @brief Set element at specified index
* @param arr Pointer to array
* @param index Index
* @param value New value
* @return true on success, false on failure
*/
bool da_set(dynamic_array_t *arr, size_t index, int value);
#endif /* DYNAMIC_ARRAY_H */c
// dynamic_array.c
#include "dynamic_array.h"
#include <stdlib.h>
#include <string.h>
#define GROWTH_FACTOR 2
#define MIN_CAPACITY 8
struct dynamic_array {
int *data;
size_t size;
size_t capacity;
};
dynamic_array_t *da_create(size_t initial_capacity) {
if (initial_capacity < MIN_CAPACITY) {
initial_capacity = MIN_CAPACITY;
}
dynamic_array_t *arr = malloc(sizeof(dynamic_array_t));
if (arr == NULL) {
return NULL;
}
arr->data = malloc(initial_capacity * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initial_capacity;
return arr;
}
void da_destroy(dynamic_array_t *arr) {
if (arr != NULL) {
free(arr->data);
free(arr);
}
}
static bool da_resize(dynamic_array_t *arr, size_t new_capacity) {
if (arr == NULL || new_capacity < arr->size) {
return false;
}
int *new_data = realloc(arr->data, new_capacity * sizeof(int));
if (new_data == NULL) {
return false;
}
arr->data = new_data;
arr->capacity = new_capacity;
return true;
}
bool da_push(dynamic_array_t *arr, int value) {
if (arr == NULL) {
return false;
}
// Resize if capacity is insufficient
if (arr->size >= arr->capacity) {
size_t new_capacity = arr->capacity * GROWTH_FACTOR;
if (!da_resize(arr, new_capacity)) {
return false;
}
}
arr->data[arr->size++] = value;
return true;
}
bool da_pop(dynamic_array_t *arr, int *value) {
if (arr == NULL || arr->size == 0) {
return false;
}
if (value != NULL) {
*value = arr->data[arr->size - 1];
}
arr->size--;
// Shrink if utilization is too low
if (arr->size > 0 && arr->size < arr->capacity / 4) {
size_t new_capacity = arr->capacity / 2;
if (new_capacity >= MIN_CAPACITY) {
da_resize(arr, new_capacity);
}
}
return true;
}
size_t da_size(const dynamic_array_t *arr) {
return (arr != NULL) ? arr->size : 0;
}
bool da_get(const dynamic_array_t *arr, size_t index, int *value) {
if (arr == NULL || index >= arr->size || value == NULL) {
return false;
}
*value = arr->data[index];
return true;
}
bool da_set(dynamic_array_t *arr, size_t index, int value) {
if (arr == NULL || index >= arr->size) {
return false;
}
arr->data[index] = value;
return true;
}c
// test_dynamic_array.c
#include "dynamic_array.h"
#include <stdio.h>
#include <assert.h>
void test_create_destroy(void) {
dynamic_array_t *arr = da_create(10);
assert(arr != NULL);
assert(da_size(arr) == 0);
da_destroy(arr);
printf("✓ test_create_destroy passed\n");
}
void test_push_pop(void) {
dynamic_array_t *arr = da_create(2);
assert(arr != NULL);
// Test push
assert(da_push(arr, 10));
assert(da_push(arr, 20));
assert(da_push(arr, 30));
assert(da_size(arr) == 3);
// Test pop
int value;
assert(da_pop(arr, &value));
assert(value == 30);
assert(da_size(arr) == 2);
da_destroy(arr);
printf("✓ test_push_pop passed\n");
}
void test_get_set(void) {
dynamic_array_t *arr = da_create(10);
assert(arr != NULL);
da_push(arr, 10);
da_push(arr, 20);
int value;
assert(da_get(arr, 0, &value));
assert(value == 10);
assert(da_set(arr, 1, 99));
assert(da_get(arr, 1, &value));
assert(value == 99);
da_destroy(arr);
printf("✓ test_get_set passed\n");
}
int main(void) {
test_create_destroy();
test_push_pop();
test_get_set();
printf("\nAll tests passed!\n");
return 0;
}📚 Recommended Learning Resources
Books
- "C Programming Language" (K&R C) - Brian Kernighan & Dennis Ritchie
- "C Primer Plus" - Stephen Prata
- "Expert C Programming" - Peter van der Linden
- "C Traps and Pitfalls" - Andrew Koenig
Online Resources
Tools
- Compilers: GCC, Clang
- Debuggers: GDB, LLDB
- Static Analysis: cppcheck, clang-tidy, Coverity
- Memory Checking: Valgrind, AddressSanitizer
- Profiling: gprof, perf, Callgrind
📝 Summary
Core Principles
- Clarity over cleverness: Write readable code, not clever code
- Security first: Always validate input, check bounds, handle errors
- Simple design: Keep functions short, single responsibility
- Consistency: Follow uniform code style and naming conventions
- Test-driven: Write tests to ensure code correctness
- Documentation: Write clear documentation for public interfaces
- Performance awareness: Understand performance implications, but don't optimize prematurely
- Continuous learning: C language is old but still evolving
Common Pitfalls
- ❌ Forgetting to free memory
- ❌ Using uninitialized variables
- ❌ Out-of-bounds array access
- ❌ Dangling pointers
- ❌ Buffer overflows
- ❌ Integer overflows
- ❌ Format string vulnerabilities
- ❌ Race conditions (multi-threading)
Quick Reference
| Scenario | Recommended Practice |
|---|---|
| String copying | Use strncpy or snprintf |
| Memory allocation | Check return values, pair with free |
| Array access | Check bounds |
| Function parameters | Use const to protect read-only parameters |
| Error handling | Return error codes, use errno |
| Header files | Use guard macros, minimize dependencies |
| Debugging | Use debug macros and tools |
| Performance | Choose appropriate data structures and algorithms |
Remember: Good C code should not only run correctly, but also be easy to understand, maintain, and extend. By following these best practices, you'll be able to write high-quality C programs!
Next Step: Apply these practices to your actual projects and improve your programming skills through continuous practice.
Related Chapters: