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 →