772 lines
16 KiB
Markdown
772 lines
16 KiB
Markdown
# 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Project includes */
|
|
#include "project/common.h"
|
|
#include "project/utils.h"
|
|
|
|
/* Module-specific includes */
|
|
#include "module/internal.h"
|
|
```
|
|
|
|
❌ **Bad:**
|
|
```c
|
|
#include "module/internal.h"
|
|
#include <string.h>
|
|
#include "project/common.h"
|
|
#include <stdio.h>
|
|
#include "project/utils.h"
|
|
#include <stdlib.h>
|
|
```
|
|
|
|
### 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 <stddef.h> // 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 <stdio.h> // For FILE, fopen, etc.
|
|
#include <string.h> // 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 <stdio.h> // Exposing implementation details
|
|
#include <string.h> // Not needed in the interface
|
|
#include <stdlib.h> // 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;
|
|
}
|
|
``` |