From 5e2c45fbffb38399d59544daa7688e2ef104403e Mon Sep 17 00:00:00 2001 From: Marnix Krul Date: Sat, 15 Mar 2025 08:57:13 +0100 Subject: [PATCH] Add c-programming-guidelines.md --- c-programming-guidelines.md | 772 ++++++++++++++++++++++++++++++++++++ 1 file changed, 772 insertions(+) create mode 100644 c-programming-guidelines.md diff --git a/c-programming-guidelines.md b/c-programming-guidelines.md new file mode 100644 index 0000000..705435e --- /dev/null +++ b/c-programming-guidelines.md @@ -0,0 +1,772 @@ +# 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; +} +``` \ No newline at end of file