# C Programming Guidelines ## 1. Naming Conventions ### 1.1 Use snake_case for variables and functions - Names should be descriptive but concise - Use lowercase with underscores for separation ✅ **Good:** ```c int user_count = 0; float calculate_average(int *values, int count); char *get_user_input(void); ``` ❌ **Bad:** ```c int UserCount = 0; // Uses PascalCase instead of snake_case float calcAvg(int *v, int c); // Unclear abbreviated names char *input(void); // Too vague ``` ### 1.2 Use UPPERCASE for constants and macros - All capitals with underscores for separation ✅ **Good:** ```c #define MAX_BUFFER_SIZE 1024 #define PI 3.14159 const int ERROR_CODE_FILE_NOT_FOUND = -1; ``` ❌ **Bad:** ```c #define maxbuffersize 1024 // Should be uppercase with underscores #define Pi 3.14159 // Should be all uppercase const int errorCode = -1; // Should be uppercase with underscores ``` ### 1.3 Use PascalCase for types - First letter of each word should be capitalized - This applies to struct, enum, union, and typedef names ✅ **Good:** ```c typedef struct { int x; int y; } Point; typedef enum { FileStatusOk, FileStatusNotFound, FileStatusPermissionDenied } FileStatus; ``` ❌ **Bad:** ```c typedef struct { int x; int y; } point; // Should start with capital P typedef enum { FILE_STATUS_OK, // Should use PascalCase, not screaming snake case for enum values FILE_STATUS_NOT_FOUND, FILE_STATUS_PERMISSION_DENIED } file_status; // Should start with capital F ``` ## 2. Code Formatting ### 2.1 Use consistent indentation - 4 spaces is the most common choice - Be consistent throughout your project ✅ **Good:** ```c if (condition) { printf("Condition is true\n"); if (another_condition) { printf("Both conditions are true\n"); } } ``` ❌ **Bad:** ```c if (condition) { printf("Condition is true\n"); if (another_condition) { printf("Both conditions are true\n"); } } ``` ### 2.2 Use consistent brace style - K&R style (opening brace on same line) is common in C - Whatever style you choose, be consistent ✅ **Good (K&R style):** ```c if (condition) { statement1; statement2; } else { statement3; statement4; } ``` ✅ **Also Good (Allman style, if used consistently):** ```c if (condition) { statement1; statement2; } else { statement3; statement4; } ``` ❌ **Bad (mixing styles):** ```c if (condition) { statement1; statement2; } else { statement3; statement4; } ``` ### 2.3 Use proper spacing around operators and keywords - Add spaces after commas and around operators - Add space after keywords like if, while, for, but not after function names ✅ **Good:** ```c for (int i = 0; i < 10; i++) { result = calculate(a + b, c * d); if (result > threshold) { break; } } ``` ❌ **Bad:** ```c for(int i=0;i<10;i++){ result=calculate(a+b,c*d); if(result>threshold){ break; } } ``` ## 3. Function Guidelines ### 3.1 Keep functions short and focused - Each function should have a single responsibility - Aim for 20-30 lines maximum when possible ✅ **Good:** ```c int validate_input(const char *input) { if (input == NULL) { return ERROR_NULL_INPUT; } if (strlen(input) > MAX_INPUT_LENGTH) { return ERROR_INPUT_TOO_LONG; } if (!contains_valid_chars(input)) { return ERROR_INVALID_CHARS; } return INPUT_VALID; } ``` ❌ **Bad:** ```c int process_input(const char *input) { // 100+ lines of code that: // - validates input // - parses input // - processes data // - updates database // - generates report // - handles errors // All mixed together } ``` ### 3.2 Use clear parameter names and document function behavior - Input parameters should come first, output parameters last - Use consistent return value semantics ✅ **Good:** ```c /** * Splits a string into tokens based on a delimiter. * * @param str String to tokenize (modified by this function) * @param delim Characters that serve as delimiters * @return Pointer to the next token or NULL if no more tokens found */ char *tokenize_string(char *str, const char *delim) { // Implementation } ``` ❌ **Bad:** ```c // No documentation, unclear parameter names char *ts(char *s, const char *d) { // Implementation } ``` ### 3.3 Check return values and handle errors - Always check return values of functions that can fail - Have explicit error paths ✅ **Good:** ```c FILE *file = fopen("data.txt", "r"); if (file == NULL) { fprintf(stderr, "Error opening file: %s\n", strerror(errno)); return ERROR_FILE_OPEN_FAILED; } // Use the file fclose(file); ``` ❌ **Bad:** ```c FILE *file = fopen("data.txt", "r"); // No error checking! // Use the file - potential NULL pointer dereference fclose(file); ``` ## 4. Memory Management ### 4.1 Always pair allocation with deallocation - Every `malloc()` needs a matching `free()` - Check for allocation failures ✅ **Good:** ```c int *data = (int *)malloc(size * sizeof(int)); if (data == NULL) { fprintf(stderr, "Memory allocation failed\n"); return ERROR_MEMORY_ALLOCATION; } // Use the allocated memory free(data); data = NULL; // Prevent use after free ``` ❌ **Bad:** ```c int *data = (int *)malloc(size * sizeof(int)); // No NULL check! // Use the allocated memory // No free - memory leak! ``` ### 4.2 Use consistent resource management patterns - Release resources in reverse order of acquisition - Consider using goto for error cleanup in C ✅ **Good:** ```c int process_data(void) { FILE *input_file = NULL; FILE *output_file = NULL; char *buffer = NULL; int result = SUCCESS; buffer = (char *)malloc(BUFFER_SIZE); if (buffer == NULL) { result = ERROR_MEMORY_ALLOCATION; goto cleanup; } input_file = fopen("input.txt", "r"); if (input_file == NULL) { result = ERROR_INPUT_FILE_OPEN; goto cleanup; } output_file = fopen("output.txt", "w"); if (output_file == NULL) { result = ERROR_OUTPUT_FILE_OPEN; goto cleanup; } // Process data using all resources cleanup: // Clean up in reverse order if (output_file != NULL) fclose(output_file); if (input_file != NULL) fclose(input_file); free(buffer); return result; } ``` ❌ **Bad:** ```c int process_data(void) { // Inconsistent error handling and resource cleanup char *buffer = (char *)malloc(BUFFER_SIZE); FILE *input_file = fopen("input.txt", "r"); if (input_file == NULL) { free(buffer); return ERROR_INPUT_FILE_OPEN; } FILE *output_file = fopen("output.txt", "w"); // Forgot to check if output_file is NULL // Process data fclose(input_file); fclose(output_file); // Might be NULL! // Forgot to free buffer - memory leak! return SUCCESS; } ``` ## 5. Header Files ### 5.1 Always use include guards - Prevent multiple inclusion with include guards - Use a unique name based on file path ✅ **Good:** ```c #ifndef PROJECT_MODULE_HEADER_H #define PROJECT_MODULE_HEADER_H // Header content #endif /* PROJECT_MODULE_HEADER_H */ ``` ❌ **Bad:** ```c // No include guard - can cause multiple definition errors typedef struct { int x; int y; } Point; void draw_point(Point p); ``` ### 5.2 Organize includes consistently - Include system headers first, then project headers - Group related declarations ✅ **Good:** ```c /* System includes */ #include #include #include /* Project includes */ #include "project/common.h" #include "project/utils.h" /* Module-specific includes */ #include "module/internal.h" ``` ❌ **Bad:** ```c #include "module/internal.h" #include #include "project/common.h" #include #include "project/utils.h" #include ``` ### 5.3 Declare what you use, use what you declare - Include only what you need - Don't rely on indirect includes ✅ **Good:** ```c // In file.h #ifndef FILE_H #define FILE_H #include // For size_t // Declare only what other modules need size_t read_file(const char *filename, char *buffer, size_t buffer_size); #endif /* FILE_H */ // In file.c #include "file.h" #include // For FILE, fopen, etc. #include // For strerror size_t read_file(const char *filename, char *buffer, size_t buffer_size) { // Implementation using stdio functions } ``` ❌ **Bad:** ```c // In file.h #ifndef FILE_H #define FILE_H #include // Exposing implementation details #include // Not needed in the interface #include // Not needed in the interface // Using stdio in the interface FILE *open_file(const char *filename); size_t read_file(FILE *file, char *buffer, size_t buffer_size); #endif /* FILE_H */ ``` ## 6. Comments and Documentation ### 6.1 Document function interfaces - Describe purpose, parameters, return values, and side effects - Document error conditions ✅ **Good:** ```c /** * Searches for a substring within a string. * * @param haystack The string to search in * @param needle The substring to search for * @return Pointer to the first occurrence of needle in haystack, * or NULL if needle is not found */ char *find_substring(const char *haystack, const char *needle) { // Implementation } ``` ❌ **Bad:** ```c // Finds a string char *find_substring(const char *s1, const char *s2) { // Implementation } ``` ### 6.2 Comment complex or non-obvious code - Explain the "why" not the "what" - Keep comments up to date with code changes ✅ **Good:** ```c // Use binary search to find insertion point while maintaining ordering // This is O(log n) rather than the O(n) approach of linear search int insertion_index = find_insertion_point(array, value, 0, array_size - 1); // Special case: The XDR protocol requires 4-byte alignment for data // so we pad the buffer with zeros as needed size_t padding = (4 - (buffer_size % 4)) % 4; memset(buffer + buffer_size, 0, padding); ``` ❌ **Bad:** ```c // This function finds the substring char *p = strstr(s, sub); // Increment i i++; // Too much detail about "what" the code does, not "why" // Loop through the array and add each element to sum int sum = 0; for (int i = 0; i < size; i++) { sum += array[i]; // Add element to sum } ``` ## 7. Error Handling ### 7.1 Use consistent error reporting - Choose one approach (return codes, errno, error objects) and stick with it - Document error codes ✅ **Good:** ```c /** * Error codes for the file module */ #define FILE_ERROR_NONE 0 #define FILE_ERROR_NOT_FOUND -1 #define FILE_ERROR_PERMISSION -2 #define FILE_ERROR_INVALID_FORMAT -3 /** * Opens a configuration file and validates its format. * * @param filename The path to the config file * @return FILE_ERROR_NONE on success, or appropriate error code on failure */ int open_config_file(const char *filename) { if (access(filename, F_OK) != 0) { return FILE_ERROR_NOT_FOUND; } if (access(filename, R_OK) != 0) { return FILE_ERROR_PERMISSION; } // Check file format if (!is_valid_config_format(filename)) { return FILE_ERROR_INVALID_FORMAT; } return FILE_ERROR_NONE; } ``` ❌ **Bad:** ```c // Inconsistent error reporting int open_file(const char *filename) { FILE *file = fopen(filename, "r"); if (file == NULL) { printf("Error opening file\n"); return -1; // Generic error code } if (!is_valid_format(file)) { fprintf(stderr, "Invalid format\n"); fclose(file); exit(1); // Abrupt program termination! } // No error, but doesn't return the file handle! fclose(file); return 0; } ``` ### 7.2 Validate function inputs - Check parameters for validity at the beginning of functions - Return early for invalid inputs ✅ **Good:** ```c int calculate_average(const int *values, size_t count, double *result) { // Validate inputs if (values == NULL || result == NULL) { return ERROR_NULL_POINTER; } if (count == 0) { return ERROR_DIVISION_BY_ZERO; } // Calculation is safe now double sum = 0; for (size_t i = 0; i < count; i++) { sum += values[i]; } *result = sum / count; return SUCCESS; } ``` ❌ **Bad:** ```c double calculate_average(int *values, int count) { // No input validation! double sum = 0; for (int i = 0; i < count; i++) { sum += values[i]; // Potential NULL pointer dereference! } return sum / count; // Potential division by zero! } ``` ## 8. Defensive Programming ### 8.1 Check array bounds - Never access arrays out of bounds - Use size parameters to track array sizes ✅ **Good:** ```c /** * Finds the maximum value in an array. */ int find_maximum(const int *array, size_t size) { if (array == NULL || size == 0) { return INT_MIN; // Error value } int max = array[0]; for (size_t i = 1; i < size; i++) { if (array[i] > max) { max = array[i]; } } return max; } ``` ❌ **Bad:** ```c int find_maximum(int *array, int size) { // No validation on array or size int max = array[0]; // Crash if array is NULL or size is 0 // Could go out of bounds if size is incorrect for (int i = 1; i <= size; i++) { // Note: <= is wrong! if (array[i] > max) { max = array[i]; } } return max; } ``` ### 8.2 Initialize variables before use - Always initialize variables - Don't rely on default initialization ✅ **Good:** ```c void process_data(void) { int count = 0; double total = 0.0; char buffer[MAX_SIZE] = {0}; // Zero-initialize array // Variables are initialized before use } ``` ❌ **Bad:** ```c void process_data(void) { int count; // Uninitialized double total; // Uninitialized char buffer[MAX_SIZE]; // Uninitialized // Using variables without initialization total = total + 10.0; // Undefined behavior } ``` ## 9. Performance Considerations ### 9.1 Prefer readability unless performance is critical - Write clear, readable code first - Optimize only when necessary and based on profiling ✅ **Good:** ```c // Clear and readable implementation double calculate_average(const int *values, size_t count) { if (values == NULL || count == 0) { return 0.0; } double sum = 0.0; for (size_t i = 0; i < count; i++) { sum += values[i]; } return sum / count; } ``` ❌ **Bad (premature optimization):** ```c // Trying to be clever with unnecessary optimization double calculate_average(const int *values, size_t count) { if (!values || !count) return 0.0; register double sum = 0.0; // 'register' rarely helps modern compilers register size_t i = count; // Reversed loop for "performance" but harder to understand while (i--) { sum += values[i]; } return sum / count; } ``` ### 9.2 Document performance-critical code - Explain the reasoning behind optimization - Note any assumptions that affect performance ✅ **Good:** ```c /** * Fast string hash function optimized for short strings. * Uses FNV-1a algorithm which has good distribution and * is quick for strings under 100 bytes. * * Time complexity: O(n) where n is string length * Performance assumption: Most strings are under 20 chars */ unsigned int hash_string(const char *str) { const unsigned int FNV_PRIME = 16777619; const unsigned int FNV_OFFSET_BASIS = 2166136261; unsigned int hash = FNV_OFFSET_BASIS; while (*str) { hash ^= (unsigned char)*str++; hash *= FNV_PRIME; } return hash; } ``` ❌ **Bad:** ```c // No documentation of algorithm choice or performance characteristics unsigned int hash_string(const char *str) { unsigned int hash = 2166136261; while (*str) { hash ^= (unsigned char)*str++; hash *= 16777619; } return hash; } ```