# C Code Review Guide > C code review guide focused on memory safety, undefined behavior, and portability. Examples assume C11. ## Table of Contents - [Pointer and Buffer Safety](#pointer-and-buffer-safety) - [Ownership and Resource Management](#ownership-and-resource-management) - [Undefined Behavior Pitfalls](#undefined-behavior-pitfalls) - [Integer Types and Overflow](#integer-types-and-overflow) - [Error Handling](#error-handling) - [Concurrency](#concurrency) - [Macros and Preprocessor](#macros-and-preprocessor) - [API Design and Const](#api-design-and-const) - [Tooling and Build Checks](#tooling-and-build-checks) - [Review Checklist](#review-checklist) --- ## Pointer and Buffer Safety ### Always carry size with buffers ```c // ? Bad: ignores destination size bool copy_name(char *dst, size_t dst_size, const char *src) { strcpy(dst, src); return true; } // ? Good: validate size and terminate bool copy_name(char *dst, size_t dst_size, const char *src) { size_t len = strlen(src); if (len + 1 > dst_size) { return false; } memcpy(dst, src, len + 1); return true; } ``` ### Avoid dangerous APIs Prefer `snprintf`, `fgets`, and explicit bounds over `gets`, `strcpy`, or `sprintf`. ```c // ? Bad: unbounded write sprintf(buf, "%s", input); // ? Good: bounded write snprintf(buf, buf_size, "%s", input); ``` ### Use the right copy primitive ```c // ? Bad: memcpy with overlapping regions memcpy(dst, src, len); // ? Good: memmove handles overlap memmove(dst, src, len); ``` --- ## Ownership and Resource Management ### One allocation, one free Track ownership and clean up on every error path. ```c // ? Good: cleanup label avoids leaks int load_file(const char *path) { int rc = -1; FILE *f = NULL; char *buf = NULL; f = fopen(path, "rb"); if (!f) { goto cleanup; } buf = malloc(4096); if (!buf) { goto cleanup; } if (fread(buf, 1, 4096, f) == 0) { goto cleanup; } rc = 0; cleanup: free(buf); if (f) { fclose(f); } return rc; } ``` --- ## Undefined Behavior Pitfalls ### Common UB patterns ```c // ? Bad: use after free char *p = malloc(10); free(p); p[0] = 'a'; // ? Bad: uninitialized read int x; if (x > 0) { /* UB */ } // ? Bad: signed overflow int sum = a + b; ``` ### Avoid pointer arithmetic past the object ```c // ? Bad: pointer past the end then dereference int arr[4]; int *p = arr + 4; int v = *p; // UB ``` --- ## Integer Types and Overflow ### Avoid signed/unsigned surprises ```c // ? Bad: negative converted to large size_t int len = -1; size_t n = len; // ? Good: validate before converting if (len < 0) { return -1; } size_t n = (size_t)len; ``` ### Check for overflow in size calculations ```c // ? Bad: potential overflow in multiplication size_t bytes = count * sizeof(Item); // ? Good: check before multiplying if (count > SIZE_MAX / sizeof(Item)) { return NULL; } size_t bytes = count * sizeof(Item); ``` --- ## Error Handling ### Always check return values ```c // ? Bad: ignore errors fread(buf, 1, size, f); // ? Good: handle errors size_t read = fread(buf, 1, size, f); if (read != size && ferror(f)) { return -1; } ``` ### Consistent error contracts - Use a clear convention: 0 for success, negative for failure. - Document ownership rules on success and failure. - If using `errno`, set it only for actual failures. --- ## Concurrency ### volatile is not synchronization ```c // ? Bad: data race volatile int stop = 0; void worker(void) { while (!stop) { /* ... */ } } // ? Good: C11 atomics _Atomic int stop = 0; void worker(void) { while (!atomic_load(&stop)) { /* ... */ } } ``` ### Use mutexes for shared state Protect shared data with `pthread_mutex_t` or equivalent. Avoid holding locks while doing I/O. --- ## Macros and Preprocessor ### Parenthesize arguments ```c // ? Bad: macro with side effects #define MIN(a, b) ((a) < (b) ? (a) : (b)) int x = MIN(i++, j++); // ? Good: static inline function static inline int min_int(int a, int b) { return a < b ? a : b; } ``` --- ## API Design and Const ### Const-correctness and sizes ```c // ? Good: explicit size and const input int hash_bytes(const uint8_t *data, size_t len, uint8_t *out); ``` ### Document nullability Clearly document whether pointers may be NULL. Prefer returning error codes instead of NULL when possible. --- ## Tooling and Build Checks ```bash # Warnings clang -Wall -Wextra -Werror -Wconversion -Wshadow -std=c11 ... # Sanitizers (debug builds) clang -fsanitize=address,undefined -fno-omit-frame-pointer -g ... clang -fsanitize=thread -fno-omit-frame-pointer -g ... # Static analysis clang-tidy src/*.c -- -std=c11 cppcheck --enable=warning,performance,portability src/ # Formatting clang-format -i src/*.c include/*.h ``` --- ## Review Checklist ### Memory and UB - [ ] All buffers have explicit size parameters - [ ] No out-of-bounds access or pointer arithmetic past objects - [ ] No use after free or uninitialized reads - [ ] Signed overflow and shift rules are respected ### API and Design - [ ] Ownership rules are documented and consistent - [ ] const-correctness is applied for inputs - [ ] Error contracts are clear and consistent ### Concurrency - [ ] No data races on shared state - [ ] volatile is not used for synchronization - [ ] Locks are held for minimal time ### Tooling and Tests - [ ] Builds clean with warnings enabled - [ ] Sanitizers run on critical code paths - [ ] Static analysis results are addressed