Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    C

    C is a general-purpose, procedural programming language that provides low-level access to system memory. It is known for its efficiency, portability, and power, making it ideal for system programming.

    C, despite being a foundational language with decades of use, has several common anti-patterns that can lead to bugs, security vulnerabilities, and maintenance problems. Here are the most important anti-patterns to avoid when writing C code.

    Copy
    // Anti-pattern: Not checking return values
    void process_file(const char* filename) {
        FILE* file = fopen(filename, "r");
        char buffer[256];
        fgets(buffer, sizeof(buffer), file); // What if file is NULL?
        // Process buffer...
        fclose(file);
    }
    
    // Better approach: Check return values
    void process_file(const char* filename) {
        FILE* file = fopen(filename, "r");
        if (file == NULL) {
            perror("Error opening file");
            return;
        }
        
        char buffer[256];
        if (fgets(buffer, sizeof(buffer), file) == NULL) {
            perror("Error reading file");
            fclose(file);
            return;
        }
        
        // Process buffer...
        fclose(file);
    }
    

    Always check return values from functions that can fail, such as memory allocation, file operations, and network calls.

    Copy
    // Anti-pattern: Potential buffer overflow
    void copy_string(char* dest, const char* src) {
        strcpy(dest, src); // No bounds checking
    }
    
    // Better approach: Use bounded string functions
    void copy_string(char* dest, size_t dest_size, const char* src) {
        strncpy(dest, src, dest_size - 1);
        dest[dest_size - 1] = '\0'; // Ensure null termination
    }
    
    // Even better: Check return values
    int copy_string(char* dest, size_t dest_size, const char* src) {
        if (dest == NULL || src == NULL || dest_size == 0) {
            return -1; // Error
        }
        
        size_t src_len = strlen(src);
        if (src_len >= dest_size) {
            // Source string won't fit in destination
            strncpy(dest, src, dest_size - 1);
            dest[dest_size - 1] = '\0';
            return -1; // Truncation occurred
        }
        
        strcpy(dest, src);
        return 0; // Success
    }
    

    Use bounded string functions and explicit buffer size management to prevent buffer overflows, which can lead to security vulnerabilities.

    Copy
    // Anti-pattern: Memory leak
    void process_data() {
        char* buffer = (char*)malloc(1024);
        if (buffer == NULL) {
            return; // Early return without freeing
        }
        
        // Process data...
        
        if (error_condition) {
            return; // Another early return without freeing
        }
        
        free(buffer);
    }
    
    // Better approach: Ensure cleanup
    void process_data() {
        char* buffer = (char*)malloc(1024);
        if (buffer == NULL) {
            return;
        }
        
        // Process data...
        
        if (error_condition) {
            free(buffer); // Free before returning
            return;
        }
        
        free(buffer);
    }
    

    Always free allocated memory, even in error paths, to prevent memory leaks.

    Copy
    // Anti-pattern: Using gets()
    void read_input() {
        char buffer[256];
        gets(buffer); // Dangerous - no bounds checking
        // Process buffer...
    }
    
    // Better approach: Use fgets()
    void read_input() {
        char buffer[256];
        if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
            // Remove newline if present
            size_t len = strlen(buffer);
            if (len > 0 && buffer[len-1] == '\n') {
                buffer[len-1] = '\0';
            }
            // Process buffer...
        }
    }
    

    Never use gets() as it has no bounds checking. Use fgets() or other bounded input functions instead.

    Copy
    // Anti-pattern: Integer overflow vulnerability
    void* allocate_buffer(size_t n_elements, size_t element_size) {
        size_t size = n_elements * element_size; // Potential overflow
        return malloc(size);
    }
    
    // Better approach: Check for overflow
    void* allocate_buffer(size_t n_elements, size_t element_size) {
        // Check for overflow
        if (n_elements > 0 && SIZE_MAX / n_elements < element_size) {
            errno = EOVERFLOW;
            return NULL; // Overflow would occur
        }
        
        size_t size = n_elements * element_size;
        return malloc(size);
    }
    

    Be aware of integer overflow, especially when calculating sizes for memory allocation.

    Copy
    // Anti-pattern: Uninitialized variable
    int calculate_sum(int count) {
        int sum; // Uninitialized
        for (int i = 1; i <= count; i++) {
            sum += i; // Using uninitialized value in first iteration
        }
        return sum;
    }
    
    // Better approach: Initialize variables
    int calculate_sum(int count) {
        int sum = 0; // Initialized
        for (int i = 1; i <= count; i++) {
            sum += i;
        }
        return sum;
    }
    

    Always initialize variables before using them to avoid undefined behavior.

    Copy
    // Anti-pattern: Missing const
    int string_length(char* str) {
        // Function doesn't modify str, but signature allows it
        return strlen(str);
    }
    
    // Better approach: Use const for read-only parameters
    int string_length(const char* str) {
        // Clearly communicates that str is not modified
        return strlen(str);
    }
    

    Use const for function parameters that should not be modified to communicate intent and enable compiler optimizations.

    Copy
    // Anti-pattern: Magic numbers
    void process_data(int value) {
        if (value > 1000) { // What does 1000 represent?
            // Handle special case
        }
        
        char buffer[256]; // Why 256?
        // Process data...
    }
    
    // Better approach: Named constants
    #define MAX_THRESHOLD 1000
    #define BUFFER_SIZE 256
    
    void process_data(int value) {
        if (value > MAX_THRESHOLD) {
            // Handle special case
        }
        
        char buffer[BUFFER_SIZE];
        // Process data...
    }
    

    Use named constants instead of magic numbers to improve code readability and maintainability.

    Copy
    // Anti-pattern: Global variables
    int g_count = 0;
    char g_buffer[1024];
    
    void increment_count() {
        g_count++;
    }
    
    void process_data() {
        // Using global variables
        strcpy(g_buffer, "data");
        increment_count();
    }
    
    // Better approach: Pass parameters
    struct Context {
        int count;
        char buffer[1024];
    };
    
    void increment_count(struct Context* ctx) {
        ctx->count++;
    }
    
    void process_data(struct Context* ctx) {
        strcpy(ctx->buffer, "data");
        increment_count(ctx);
    }
    

    Avoid global variables as they create hidden dependencies and make code harder to test and reason about.

    Copy
    // Anti-pattern: No header guards
    // file: utils.h
    struct Point {
        int x;
        int y;
    };
    
    void print_point(struct Point p);
    
    // Better approach: Use header guards
    // file: utils.h
    #ifndef UTILS_H
    #define UTILS_H
    
    struct Point {
        int x;
        int y;
    };
    
    void print_point(struct Point p);
    
    #endif // UTILS_H
    

    Always use header guards to prevent multiple inclusion of header files, which can lead to compilation errors.

    Copy
    // Anti-pattern: Unsafe void* usage
    void process_data(void* data, int type) {
        if (type == 0) {
            int* int_data = (int*)data;
            // Process int data...
        } else if (type == 1) {
            char* char_data = (char*)data;
            // Process char data...
        }
    }
    
    // Better approach: Use a union with a type tag
    enum DataType { INT_TYPE, CHAR_TYPE };
    
    struct Data {
        enum DataType type;
        union {
            int int_value;
            char* char_value;
        } value;
    };
    
    void process_data(struct Data* data) {
        if (data->type == INT_TYPE) {
            // Process int data...
            int value = data->value.int_value;
        } else if (data->type == CHAR_TYPE) {
            // Process char data...
            char* value = data->value.char_value;
        }
    }
    

    Avoid using void* without proper type checking. Use tagged unions or other type-safe alternatives when possible.

    Copy
    // Anti-pattern: Missing function prototype
    // file: main.c
    int main() {
        int result = add(5, 3); // No prototype, compiler assumes int add()
        printf("Result: %d\n", result);
        return 0;
    }
    
    // file: math.c
    int add(int a, int b) {
        return a + b;
    }
    
    // Better approach: Use function prototypes
    // file: math.h
    #ifndef MATH_H
    #define MATH_H
    
    int add(int a, int b);
    
    #endif // MATH_H
    
    // file: main.c
    #include "math.h"
    
    int main() {
        int result = add(5, 3); // Proper prototype available
        printf("Result: %d\n", result);
        return 0;
    }
    

    Always use function prototypes to enable compiler type checking and avoid implicit declarations.

    Copy
    // Anti-pattern: Not checking for NULL
    void process_data(size_t size) {
        int* buffer = (int*)malloc(size * sizeof(int));
        buffer[0] = 42; // Crash if malloc returned NULL
        // Process buffer...
        free(buffer);
    }
    
    // Better approach: Check for NULL
    void process_data(size_t size) {
        int* buffer = (int*)malloc(size * sizeof(int));
        if (buffer == NULL) {
            perror("Failed to allocate memory");
            return;
        }
        
        buffer[0] = 42;
        // Process buffer...
        free(buffer);
    }
    

    Always check if memory allocation functions like malloc return NULL before using the allocated memory.

    Copy
    // Anti-pattern: Unsafe string functions
    void concatenate_strings(char* dest, const char* src1, const char* src2) {
        strcpy(dest, src1); // No bounds checking
        strcat(dest, src2); // No bounds checking
    }
    
    // Better approach: Use bounded string functions
    void concatenate_strings(char* dest, size_t dest_size, const char* src1, const char* src2) {
        size_t remaining = dest_size;
        
        // Copy first string with bounds checking
        strncpy(dest, src1, remaining - 1);
        dest[remaining - 1] = '\0'; // Ensure null termination
        
        // Calculate remaining space
        size_t used = strlen(dest);
        if (used < remaining - 1) {
            // Append second string with bounds checking
            strncat(dest, src2, remaining - used - 1);
        }
    }
    

    Avoid using unsafe string functions like strcpy and strcat. Use bounded alternatives like strncpy and strncat, or better yet, use safer string handling libraries.

    Copy
    // Anti-pattern: Switch without default
    void process_command(int cmd) {
        switch (cmd) {
            case CMD_OPEN:
                // Handle open
                break;
            case CMD_CLOSE:
                // Handle close
                break;
            case CMD_READ:
                // Handle read
                break;
            // No default case - what if cmd is invalid?
        }
    }
    
    // Better approach: Include default case
    void process_command(int cmd) {
        switch (cmd) {
            case CMD_OPEN:
                // Handle open
                break;
            case CMD_CLOSE:
                // Handle close
                break;
            case CMD_READ:
                // Handle read
                break;
            default:
                fprintf(stderr, "Unknown command: %d\n", cmd);
                // Handle unknown command
                break;
        }
    }
    

    Always include a default case in switch statements to handle unexpected values.

    Copy
    // Anti-pattern: Code with subtle bugs
    int calculate_average(int* values, int count) {
        int sum = 0;
        for (int i = 0; i <= count; i++) { // Off-by-one error
            sum += values[i];
        }
        return sum / count;
    }
    
    // Better approach: Use static analysis tools
    // $ gcc -Wall -Wextra -Werror -pedantic file.c
    // $ clang --analyze file.c
    // $ cppcheck file.c
    
    int calculate_average(int* values, int count) {
        if (values == NULL || count <= 0) {
            return 0; // Handle edge cases
        }
        
        int sum = 0;
        for (int i = 0; i < count; i++) { // Correct loop bounds
            sum += values[i];
        }
        return sum / count;
    }
    

    Use static analysis tools and compiler warnings to catch common bugs and issues in your code.

    Copy
    // Anti-pattern: Unsafe macros
    #define SQUARE(x) x * x
    
    int result = SQUARE(2 + 3); // Expands to 2 + 3 * 2 + 3 = 11, not 25!
    
    // Better approach: Safer macros with parentheses
    #define SQUARE(x) ((x) * (x))
    
    // Even better: Use inline functions (C99)
    static inline int square(int x) {
        return x * x;
    }
    

    Prefer inline functions over macros when possible, as they provide type checking and avoid common macro pitfalls.

    Copy
    // Anti-pattern: Assuming valid input
    int divide(int a, int b) {
        return a / b; // Crashes if b is 0
    }
    
    // Better approach: Defensive programming
    int divide(int a, int b, int* success) {
        if (b == 0) {
            if (success != NULL) {
                *success = 0; // Indicate failure
            }
            return 0; // Default value
        }
        
        if (success != NULL) {
            *success = 1; // Indicate success
        }
        return a / b;
    }
    

    Use defensive programming techniques to handle invalid inputs and edge cases.

    Copy
    // Anti-pattern: Inconsistent error handling
    int process_file(const char* filename) {
        FILE* file = fopen(filename, "r");
        if (file == NULL) {
            return -1; // Error code, but what does -1 mean?
        }
        
        // Process file...
        
        fclose(file);
        return 0; // Success
    }
    
    // Better approach: Use consistent error codes
    // Define error codes
    #define SUCCESS 0
    #define ERROR_FILE_NOT_FOUND 1
    #define ERROR_READ_FAILED 2
    #define ERROR_INVALID_FORMAT 3
    
    int process_file(const char* filename) {
        FILE* file = fopen(filename, "r");
        if (file == NULL) {
            return ERROR_FILE_NOT_FOUND;
        }
        
        // Process file...
        
        fclose(file);
        return SUCCESS;
    }
    

    Use consistent, well-documented error codes or an error handling system to communicate failures.

    C++Go
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.