← C EnglishChapter 13 of 13

Best Practices

## Learning Objectives - Write clean, maintainable C code - Follow naming conventions - Master debugging techniques - Apply effective testing ## Code Style ### Naming Conventions ```c // Variables: snake_case int user_age; char *file_name; size_t buffer_size; // Constants: UPPER_SNAKE_CASE #define MAX_BUFFER_SIZE 1024 const int MAX_RETRIES = 3; // Functions: snake_case int calculate_total(void); void process_data(const char *input); // Types/Structs: snake_case with _t suffix typedef struct { int x; int y; } point_t; ``` ### Formatting ```c // Use 4 spaces for indentation // Line length: ~80 characters max // One declaration per line int age; char *name; size_t count; // Braces always if (condition) { do_something(); } else { do_other(); } // Function declarations int main(void) { return 0; } ``` ## Initialization ### Always Initialize Variables ```c // Bad - contains garbage int count; // Good - explicitly initialized int count = 0; // Good - zero-initialized int *ptr = NULL; char buffer[100] = {0}; ``` ### Initialize Pointers to NULL ```c // Good practice int *ptr = NULL; // Check before dereferencing if (ptr != NULL) { *ptr = 42; } ``` ## Memory Management ### Always Free Memory ```c int *ptr = (int*)malloc(sizeof(int)); if (ptr == NULL) { return 1; } // Use memory... free(ptr); ptr = NULL; // Prevent dangling pointer ``` ### Check Allocation ```c int *ptr = (int*)malloc(sizeof(int)); if (ptr == NULL) { // Handle error fprintf(stderr, "Allocation failed\n"); return 1; } ``` ### Free in Reverse Order ```c // If allocating linked nodes struct Node *n1 = malloc(sizeof(struct Node)); struct Node *n2 = malloc(sizeof(struct Node)); n1->next = n2; // Free in reverse free(n2); free(n1); ``` ## Functions ### Use const for Read-Only Parameters ```c // Good - indicates str is not modified size_t stringLength(const char *str) { size_t len = 0; while (str[len] != '\0') { len++; } return len; } ``` ### Return Error Codes ```c typedef enum { SUCCESS = 0, ERROR_NULL_POINTER = 1, ERROR_OUT_OF_MEMORY = 2, ERROR_INVALID_INPUT = 3 } error_code_t; error_code_t processData(const char *input, int *output) { if (input == NULL || output == NULL) { return ERROR_NULL_POINTER; } // Process... return SUCCESS; } ``` ### Small Functions are Good ```c // Good - single responsibility int isEven(int x) { return x % 2 == 0; } int isOdd(int x) { return x % 2 != 0; } ``` ## Arrays and Strings ### Size Calculation ```c int arr[10]; size_t size = sizeof(arr) / sizeof(arr[0]); ``` ### Bounds Checking ```c int getElement(const int *arr, size_t size, size_t index) { if (index >= size) { // Handle error return 0; } return arr[index]; } ``` ### Null-Terminate Strings ```c char buffer[100]; strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; ``` ## Error Handling ### Check Return Values ```c FILE *fp = fopen("file.txt", "r"); if (fp == NULL) { perror("Failed to open file"); return 1; } // Use file... if (fclose(fp) != 0) { perror("Failed to close file"); return 1; } ``` ### Use assert for Debugging ```c #include void process(int *data, size_t size) { assert(data != NULL); assert(size > 0); // Process... } ``` ### Disable in Production ```c #define NDEBUG // Before #include #include ``` ## Comments ### Use Comments Wisely ```c // Good: explains WHY, not WHAT // Skip first line (contains header) if (line[0] == '#') { continue; } // Bad: redundant int i = 0; // Initialize i to 0 ``` ### Document Functions ```c /** * Calculates the sum of two integers. * * @param a First integer * @param b Second integer * @return Sum of a and b */ int add(int a, int b) { return a + b; } ``` ## Portability ### Fixed-Size Types ```c #include int32_t i32 = 42; // Exactly 32 bits uint64_t u64 = 100; // Exactly 64 bits unsigned intptr_t ptr = (intptr_t)&x; // Holds pointer ``` ### Avoid Assumptions About Size ```c // Bad - assumes 4 bytes long x = 1000000; // Good - use fixed-size or limit int32_t x = 1000000; ``` ### Portable Printing ```c #include int64_t big = 9000000000000000000LL; printf("%" PRId64 "\n", big); ``` ## Security ### Avoid Buffer Overflow ```c // Bad - buffer overflow possible char buffer[100]; gets(buffer); // Good - safe input fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, "\n")] = '\0'; ``` ### Validate Input ```c if (strlen(input) >= MAX_INPUT) { // Handle error } ``` ### Use Safe String Functions ```c // Use strncpy instead of strcpy strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0'; // Use strncat instead of strcat strncat(dest, src, sizeof(dest) - strlen(dest) - 1); ``` ## Testing ### Unit Testing Example ```c #include int add(int a, int b) { return a + b; } void testAdd(void) { assert(add(2, 3) == 5); assert(add(-1, 1) == 0); assert(add(0, 0) == 0); assert(add(-5, -5) == -10); } int main(void) { testAdd(); printf("All tests passed!\n"); return 0; } ``` ## Debugging ### valgrind ```bash valgrind --leak-check=full ./program ``` ### gcc Warnings ```bash gcc -Wall -Wextra -Werror -pedantic program.c -o program ``` | Flag | Description | |------|-------------| | `-Wall` | Enable all warnings | | `-Wextra` | Extra warnings | | `-Werror` | Treat warnings as errors | | `-pedantic` | ISO C compliance | | `-g` | Include debug symbols | | `-O2` | Optimize (for performance testing) | ## Performance ### Avoid Premature Optimization ```c // Clear code first for (int i = 0; i < n; i++) { sum += arr[i]; } // Optimize only if profiling shows it's needed ``` ### Use Inline Functions (C99) ```c inline int max(int a, int b) { return a > b ? a : b; } ``` ## Summary - Use meaningful names (snake_case for variables) - Always initialize variables - Check allocation return values - Free memory and set to NULL - Use const for read-only parameters - Enable compiler warnings (-Wall -Wextra) - Use valgrind to detect memory leaks - Write tests for critical functions - Validate all input - Use fixed-size types for portability

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →