Skip to content

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

  1. Code Style and Formatting
  2. Naming Conventions
  3. Function Design Principles
  4. Memory Management
  5. Error Handling
  6. Header File Management
  7. Performance Optimization
  8. Secure Programming
  9. Debugging Techniques
  10. 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);  // Bad

Memory 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 definitions

Header 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);

#endif

Minimizing 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 bytes

8️⃣ 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 changes

Memory 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;
}

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

  1. Clarity over cleverness: Write readable code, not clever code
  2. Security first: Always validate input, check bounds, handle errors
  3. Simple design: Keep functions short, single responsibility
  4. Consistency: Follow uniform code style and naming conventions
  5. Test-driven: Write tests to ensure code correctness
  6. Documentation: Write clear documentation for public interfaces
  7. Performance awareness: Understand performance implications, but don't optimize prematurely
  8. 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

ScenarioRecommended Practice
String copyingUse strncpy or snprintf
Memory allocationCheck return values, pair with free
Array accessCheck bounds
Function parametersUse const to protect read-only parameters
Error handlingReturn error codes, use errno
Header filesUse guard macros, minimize dependencies
DebuggingUse debug macros and tools
PerformanceChoose 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:

Content is for learning and research only.