Dr. Roger Ianjamasimanana

Coding standards in C programming

By Dr. Roger Ianjamasimanana

Following coding standards allows programmers to produce maintainable, readable, and robust C code. This lesson cover basic and advanced coding standards in C.

1. Naming conventions

Properly naming variables, functions, and structures are important so that other programmers understand what your codes intend to do when they look at them.

  • Variables & functions: Use lower_case_with_underscores. e.g., first_number, add_numbers().
  • Constants (macros or global constants): Use ALL_CAPS_WITH_UNDERSCORES, e.g. MAX_BUFFER_SIZE.
  • Types (typedef, struct, enum): Some prefer TitleCase or snake_case with a _t suffix, e.g. Person_t.
  • Function names are often verbs: check_for_errors(), dump_data_to_file().
  • If a variable or function represents a measurable quantity, include the unit in the name: timeout_msecs, my_weight_lbs.
  • Use prefixes like is_ for booleans, get_ and set_ for accessors/mutators, sc_ for struct member prefixes, etc.

// Example: naming conventions in C

#include 

#define MAX_BUFFER_SIZE 1024  // ALL_CAPS for constants

typedef struct Person_t {
  char name[50];
  int age;
} Person;

int add_numbers(int first_number, int second_number) {
  return first_number + second_number;
}

int main(void) {
  Person john_doe;
  john_doe.age = 30;
  
  int result = add_numbers(3, 4);
  printf("Result: %d\n", result);

  return 0;
}

2. Indentation & spacing

  • Use consistent indentation, typically 4 spaces per level. Avoid tabs or be consistent if you must use them.
  • Keep lines short—ideally under 80 characters.
  • Use spaces around operators =, +, -, etc.
// Indentation example

int multiply(int a, int b) {
  // 4-space indentation
  return a * b;
}

int main(void) {
  int result = multiply(10, 20);
  printf("%d\n", result);
  return 0;
}

3. Writing meaningful comments

  • Inline comments (// ...) for short explanations and clarifications.
  • Block comments (/* ... */) for high-level or multi-line explanations.
  • Comment the why and intent, not just the what.
  • Use Doxygen-like tags (@param, @return, @todo, @bug) if you generate documentation automatically.
/**
* @brief   Calculates the factorial of n.
* @param   n The number for which we compute factorial.
* @return  The factorial value, or -1 if n < 0.
*/
int factorial(int n) {
  if (n < 0) {
      // Return -1 for invalid input
      return -1;
  }
  if (n == 0 || n == 1) {
      return 1;
  }

  int result = 1;
  for (int i = 2; i <= n; i++) {
      result *= i;  // multiply each i
  }
  return result;
}

4. Function & file structure

  • Keep each function focused on a single task; avoid very long functions.
  • Use static for functions that are internal to one file only.
  • Separate interface (.h) and implementation (.c) clearly.
// my_math.h
#ifndef MY_MATH_H
#define MY_MATH_H

int factorial(int n);
int fibonacci(int n);

#endif // MY_MATH_H

// my_math.c
#include "my_math.h"

int factorial(int n) {
  // ...
}

int fibonacci(int n) {
  // ...
}

5. Variable names & pointer usage

Variable names standard

  • Use all lowercase with underscores, e.g. error_count.
  • One variable per line if possible (so each can be documented or initialized clearly).
int handle_error(int error_number) {
  int            error_code = OsErr();
  Time           time_of_error;
  ErrorProcessor error_processor;
  // ...
}

Pointer variables

  • Place the * close to the variable name: char *name.
  • Initialize pointers to NULL when declared.
  • Avoid unnecessary pointer arithmetic; prefer array indexing.
// Correct pointer usage

#include 
#include 

int main(void) {
  int *dynamic_array = NULL;
  
  dynamic_array = (int *)malloc(5 * sizeof(int));
  if (dynamic_array == NULL) {
      perror("malloc failed");
      return 1;
  }
  
  for (int i = 0; i < 5; i++) {
      dynamic_array[i] = i * i;
      printf("%d ", dynamic_array[i]);
  }
  
  free(dynamic_array);
  dynamic_array = NULL;
  return 0;
}

6. Names of structure & organization

  • Use _ to separate words in structure names or members.
  • Organize members to minimize alignment padding (group ints, then pointers, etc.).
  • Declare each struct member on its own line (except bitfields).
  • Keep major structures in .h files if shared, or at top of the .c if private.
// Example struct

struct foo {
  struct foo   *next;    /* linked list of 'foo' */
  struct mumble amumble; /* an embedded struct */
  int           bar;
  unsigned int  baz : 1, /* bitfield members */
                fuz : 5,
                zap : 2;
  uint8_t       flag;
};

struct foo *foohead; /* Head pointer to a global foo list */

7. Defensive programming & avoiding magic numbers

  • Check boundary conditions (e.g., array indexes, string lengths).
  • Fail fast rather than ignoring invalid input or conditions.
  • Use named constants, enum labels, or const variables instead of "magic numbers."
#include 

#define ARRAY_SIZE 5
static const int MAX_RETRIES = 3;

int main(void) {
  int my_array[ARRAY_SIZE] = {0, 1, 2, 3, 4};
  int index = 6;  // Potentially out-of-range
  
  if (index < 0 || index >= ARRAY_SIZE) {
      printf("Index %d out of bounds\n", index);
      return 1;
  }
  printf("Value at %d: %d\n", index, my_array[index]);
  
  // use MAX_RETRIES for retry logic, etc.
  // ...
  return 0;
}

8. Global variables & constants

  • Prepend g_ to global variables, e.g. g_log, g_plog.
  • Avoid global variables whenever possible.
  • Global constants in ALL CAPS with underscores: MY_GLOBAL_CONSTANT.
Logger  g_log;
Logger *g_plog;
const int A_GLOBAL_CONSTANT = 5;

9. Defining macro names

  • Macros should be in ALL CAPS with underscores, e.g. #define MAX_BUFFER_SIZE 1024.
  • Always wrap macro parameters in parentheses to avoid precedence issues.
  • If a macro spans multiple statements, wrap in do { ... } while (0).
  • For type safety, prefer inline functions to macros when possible.
// Macro examples

#define IS_ERR(err) ((err) < 0)
#define MAX(a, b)   ((a) > (b) ? (a) : (b))

// Multi-line macro
#define MACRO(v, w, x, y)          \
do {                               \
  v = (x) + (y);                 \
  w = (y) + 2;                   \
} while (0)

// Inline function replacement for max
inline int max_int(int x, int y) {
  return (x > y ? x : y);
}

10. Enum names

  • Use ALL CAPS with underscores for enum labels, e.g. PIN_OFF, PIN_ON.
  • Include an error or uninitialized state if needed.
// Simple enum example

enum PinStateType {
  PIN_OFF,
  PIN_ON
};

// Enum with an error state
enum {
  STATE_ERR,
  STATE_OPEN,
  STATE_RUNNING,
  STATE_DYING
};

11. Error handling

  • Check return values of library calls (file I/O, memory allocation, etc.).
  • Use errno or other error codes; call perror() for system errors.
  • For malloc and realloc, check returned pointers; or wrap them in your own function that handles failures consistently.
#include 
#include 

int main(void) {
  FILE *fp = fopen("file.txt", "r");
  if (!fp) {
      perror("fopen failed");
      return EXIT_FAILURE;
  }
  // ...
  fclose(fp);
  return EXIT_SUCCESS;
}

12. Documentation

  • Document the intent and reasoning behind decisions.
  • Use Doxygen or similar to auto-generate docs.
  • Explain why something was done, not just what.
  • Add @todo, @bug, @see tags where relevant.
/**
* @brief  Checks if a given state is valid.
* @param  state The state to check.
* @return 1 if valid, 0 if invalid.
* @todo   Add more states in next release.
*/
int is_valid_state(int state) {
  // ...
}

13. Formatting & braces

Brace placement:

K&R style is recommended:

if (condition) {
  ...
}

while (condition) {
  ...
}

Always use braces:

Even if there's only one statement, braces help avoid errors when adding code later.

if (some_value == 1) {
  some_value = 2;
}

One-line form is acceptable if truly short:


if (some_value == 1) some_value = 2;

Parentheses for keywords vs. functions

  • Space after keywords: if (condition), while (condition).
  • No space before function calls: strcpy(dest, src);
  • return 1; (no need for return (1);).

Closing brace comments

Comment on closing braces in deeply nested blocks:

while (1) {
  if (valid) {
      ...
  } /* if valid */
  else {
      ...
  } /* else not valid */
} /* end while(1) */

Line width

Keep lines under 80 characters for better readability (especially in diffs and terminals).

14. If-Then-Else & switch

  • Constant on the left: if (6 == errorNum) so the compiler catches = typos.
  • switch:
    • Comment fall-through cases explicitly.
    • Always provide a default case to handle unrecognized values.
    • Put new variable declarations in a { ... } block if needed.
switch (value) {
case 1:
  ...
  /* fall-through */
case 2: {
  int temp = 42;
  ...
}
break;
default:
  printf("Unexpected value: %d\n", value);
  break;
}

15. Using goto, continue, break, and ?:

Using goto

Use sparingly—often only for error cleanup or breaking out of deeply nested loops. Label clearly:

for (...) {
  while (...) {
      if (disaster) {
          goto error;
      }
  }
}
...
error:
  // cleanup code

Using continue & break

They are effectively small goto jumps. Use carefully, ensuring you don’t skip vital code.

Ternary operator (?:)

  • Put the condition in parentheses.
  • Keep the true/false branches simple or function calls.
// Acceptable usage
return (condition) ? func1() : func2();

16. One statement per line

Keep each statement on its own line. Also, only one variable per line if possible. This way, each variable can be documented and initialized clearly.

// Not recommended:
char *name, address;

// Recommended:
char *name = NULL;    // pointer to string
char  address = '\0'; // single character

17. Enums vs. #define vs. const

  • Enums are understood by debuggers and usually don’t occupy extra space. Great for named integral constants.
  • const variables are type-safe but may occupy space in some compilers.
  • #define is a simple text replacement, so the debugger sees only numbers.

18. Header file guards

Always guard header files to prevent multiple inclusion:

// my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H

// content

#endif // MY_HEADER_H

19. Mixing C & C++

  • Use extern "C" when including C headers in C++.
  • Guard with #ifdef __cplusplus to keep the code valid in both C and C++.
// Example: mixing C and C++

#ifdef __cplusplus
extern "C" {
#endif

void my_c_function(int arg);

#ifdef __cplusplus
}
#endif

20. No data definitions in headers

  • Do not define global variables in header files. Instead, declare them as extern in the header and define them in a .c file.
  • This prevents multiple definitions linking errors and keeps the code structure clean.

21. Layering

Divide the system into layers that communicate via well-defined interfaces. If you must call a non-adjacent layer, document why. This approach controls complexity and reduces code breakage when layers change.

22. Miscellaneous guidelines

  • Avoid using floating-point for discrete values or loop counters. Compare floats with <= or >=, never ==.
  • Do not rely on automatic beautifiers alone. A human can better present the intent in whitespace and layout.
  • Be const-correct: use const for pointers and parameters that must not change data.
  • Use #if for compile-time flags instead of #ifdef, so -DDEBUG=0 won’t accidentally enable debug code.

23. Conclusion

By merging these guidelines into a single, coherent coding standard, you have comprehensive rules for naming, commenting, indentation, error handling, layering, documentation, and more. Consistency and clarity are key: strive to write code that both humans and compilers love.

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access