main-c-underscore-coding-underscore-style.md
Version : 1.0
This document establishes coding conventions for modern C projects, emphasizing clarity, consistency, and best practices. The style prioritizes readability and maintainability while leveraging C’s type system for enhanced safety and expressiveness.
Each rule has a unique identifier (e.g., AVMCCS-L001) for easy reference in reviews and tooling.
This guide uses US English spelling conventions (e.g., “Color” not “Colour”, “initialize” not “initialise”).
Note : This guide complements but does not replace domain-specific standards. For safety-critical systems, applicable standards like MISRA C take precedence. This guide’s purpose focuses on coding style and common practices rather than comprehensive coverage of all C programming topics.
Note on examples : Code snippets throughout this guide are simplified to illustrate specific concepts. They may omit elements required by other rules (module prefixes, static keywords, error handling, etc.) for clarity. Always apply all relevant rules when writing production code.
This guide assumes C11 or later (C99 at minimum):
Declare variables where needed , not just at function start [AVMCCS-L001]
Empty parameter lists : Use (void) for functions with no parameters [AVMCCS-L002]
Use modern headers : <stdint.h>, <stdbool.h>, etc. [AVMCCS-L003]
K&R style variant : [AVMCCS-F001]
Mandatory braces : Always use braces, even for single-statement blocks [AVMCCS-F002]
4 spaces : Use 4 space indentation (no tabs) [AVMCCS-F003]
Good:
typedef struct{int x;int y;} Point;void f(bool reverse){if (reverse) {puts("!dlroW olleH");} else {puts("Hello World");}}
Bad:
typedef struct {int x;int y;} Point;void f(bool reverse) {if (reverse)puts("!dlroW olleH");elseputs("Hello World");}
Pointer placement : The * belongs with the variable name, not the type [AVMCCS-F004]
Variable declarations : Generally declare one variable per line [AVMCCS-F005]
Line length : Keep lines below 100 columns [AVMCCS-F006]
Function spacing : Separate function definitions with exactly one empty line [AVMCCS-F008]
Readability over brevity : Prefer clear, self-documenting code
No trailing whitespace : Lines must not end with spaces or tabs [AVMCCS-F009]
Files end with newline : All source files must end with a newline character [AVMCCS-F010]
Clean git commits : Configure your editor to remove trailing whitespace
Return statements : Separate with empty line when function body > 3 lines [AVMCCS-F011]
Logical blocks : Use empty lines to separate steps within functions [AVMCCS-F012]
Control flow blocks : Compact style for short blocks, spaced for longer ones [AVMCCS-F013]
Switch statements : Separate cases with empty lines when > 3 lines per case [AVMCCS-F014]
Control structures : Space after keywords, before opening brace [AVMCCS-F015]
Function calls : No space between function name and parentheses [AVMCCS-F016]
Comments : Space after comment markers [AVMCCS-F017]
Casts : Space after closing parenthesis [AVMCCS-F018]
Hexadecimal numbers : Use uppercase letters [AVMCCS-F019]
Binary operators : Use spaces around binary operators [AVMCCS-F020]
Initializer braces : Always place on the same line as the declaration [AVMCCS-F021]
Follow consistent order for declaration specifiers:
Functions : [storage] [function-specifier] [return-type]
// Good: Consistent orderstatic inline void helper(void);static inline void fast\_helper(void);extern void public\_api(void);static inline const char \*get\_name(void);static void process\_data(void);inline void fast\_operation(void);// Bad: Inconsistent orderinline static void helper(void);// Wrong: inline before staticvoid static process\_data(void);// Wrong: static after voidconst static char \*get\_name(void);// Wrong: const before static
Variables : [storage] [type-qualifier] [type]
// Good: Consistent orderstatic const int max\_value = 100;extern volatile sig\_atomic\_t signal\_flag;static \_Thread\_local int thread\_counter;static const char \*const messages[] = { "Hello", "World" };extern const size\_t buffer\_size;static volatile uint32\_t \*hardware\_reg;// Bad: Inconsistent orderconst static int max\_value = 100;// Wrong: const before staticvolatile extern sig\_atomic\_t signal\_flag;// Wrong: volatile before externconst char static \*const messages[] = {};// Wrong: static in middle
Additional specifiers :
restrict: Use for pointer parameters that don’t alias [AVMCCS-L005]
_Atomic: For atomic operations (C11) [AVMCCS-L006]
volatile: For hardware registers or signal handlers [AVMCCS-L007]
Prefer post-increment (i++) over pre-increment (++i):
// Good: Idiomatic C stylefor (int i = 0; i \< n; i++) { ... }// Avoid using increment/decrement for their return values// Bad: Confusingint b = ++a;array[++index] = value;// Good: Separate statements for claritya++;int b = a;// Exception: Idiomatic patterns are OKwhile (\*dst++ = \*src++) { }// String copy idiom
Note : This is a stylistic preference for readability and C idiom consistency. For primitive types like int, modern compilers generate identical code for i++ and ++i when the return value is not used (this is true for C with an optimized release build). The post-increment form i++ is more commonly seen in C code and reads more naturally. This guide chooses i++ for consistency with established C conventions.
Use guard clauses to reduce indentation:
// Good: Early exit patternint process\_value(int n){if (n \< 0) {return -1;}// Main logic with reduced nestingcompute\_result(n);return n \* 2;}// Bad: Unnecessary nestingint process\_value(int n){if (n \>= 0) {compute\_result(n);return n \* 2;}return -1;}
Use switch when testing one variable for 3+ values:
// OK: Two comparisonsif (state == 1) {handle\_init();} else if (state == 2) {handle\_running();} else {handle\_error();}// Bad: Should use switch for 3+ casesif (state == 1) {handle\_init();} else if (state == 2) {handle\_running();} else if (state == 3) {handle\_shutdown();} else {handle\_error();}// Good: Switch for multiple casesswitch (state) {case 1:handle\_init();break;case 2:handle\_running();break;case 3:handle\_shutdown();break;default:handle\_error();break;}
Use goto for clean error handling and resource cleanup:
// Good: Initialize pointers to NULL for clean cleanupparse\_config\_result\_t parse\_config(const char \*path, Config \*\*out){FILE \*file = fopen(path, "r");if (!file) {return ParseConfigFileOpenFailed;}char \*buffer = NULL;Config \*config = NULL;parse\_config\_result\_t result = ParseConfigOk;buffer = malloc(BUFFER\_SIZE);if (!buffer) {result = ParseConfigOutOfMemory;goto cleanup;}config = calloc(1, sizeof(Config));if (!config) {result = ParseConfigOutOfMemory;goto cleanup;}// ... parsing logic ...\*out = config;config = NULL;// Transfer ownershipcleanup:free(config);// Safe: free(NULL) is a no-opfree(buffer);fclose(file);return result;}
Important : Use goto only for well-established patterns:
Error handling and resource cleanup (shown above)
Breaking out of deeply nested loops when refactoring isn’t practical
State machines in performance-critical code
Never use goto for general flow control, jumping backwards, or creating spaghetti code. The cleanup pattern works because it follows strict rules: labels at the end, only jumping forward, and maintaining clear resource ownership. If you’re unsure whether your goto usage is appropriate, refactor to use functions or structured control flow instead.
Use _Static_assert for compile-time validation:
// Verify struct sizes and alignments\_Static\_assert(sizeof(DateTime) == 24, "DateTime size changed");\_Static\_assert(offsetof(Packet, data) == 8, "Packet layout assumption violated");// Verify configuration consistency\_Static\_assert(MAX\_BUFFER\_SIZE \>= MIN\_BUFFER\_SIZE, "Buffer size configuration error");// Type size assumptions\_Static\_assert(sizeof(int) == 4, "Code assumes 32-bit int");// Verify enum values don't overflow storage\_Static\_assert(ConnectionStateMax \<= 255, "connection\_state\_t values exceed uint8\_t");
Where to use:
After type definitions to verify layout assumptions [AVMCCS-L012]
In implementation files to verify platform assumptions [AVMCCS-L013]
To ensure configuration constants are consistent [AVMCCS-L014]
Macros require special care to avoid common pitfalls:
Always parenthesize macro parameters [AVMCCS-L015]
Parenthesize the entire macro expression [AVMCCS-L016]
Use do { ... } while (0) for multi-statement macros [AVMCCS-L017]
Avoid expressions with side effects as macro arguments [AVMCCS-L018]
Protect against variable shadowing in complex macros [AVMCCS-L019]
// Good: All parameters parenthesized#define MAX(a, b) ((a) \> (b) ? (a) : (b))#define SQUARE(x) ((x) \* (x))#define ARRAY\_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))// Bad: Unprotected parameters#define MAX(a, b) (a \> b ? a : b) // Wrong: MAX(x&1, y) breaks#define SQUARE(x) x \* x // Wrong: SQUARE(a+b) = a+b\*a+b// Watch for side effects in macro usage:int x = 5;int bad = MAX(x++, 10);// Bug: x may increment twiceint good = MAX(x, 10);// Safe: no side effectsx++;// Increment separately if needed// Good: Multi-statement macros use do-while#define SWAP(a, b) do { \ typeof(a) tmp = (a); \ (a) = (b); \ (b) = tmp; \} while (0)// Bad: No do-while protection#define SWAP(a, b) { \ typeof(a) tmp = (a); \ (a) = (b); \ (b) = tmp; \}// This breaks: if (x) SWAP(x, y); else ...// Note: typeof was a GCC extension, now part of the standard since C23
Side Effects Warning : Function-like macros evaluate arguments multiple times
// WARNING: Function-like macros evaluate arguments multiple times#define MAX(a, b) ((a) \> (b) ? (a) : (b))#define MIN(a, b) ((a) \< (b) ? (a) : (b))// Dangerous: Side effects happen multiple times!int y = MAX(x++, 10);// x incremented twice if x \> 10int z = MIN(expensive\_function(), threshold);// function called twice// Safe: Use intermediate variablesint tmp = expensive\_function();int z = MIN(tmp, threshold);
Variable Shadowing in Macros :
// Problem: Macro variables can shadow caller's variables#define SWAP(a, b) do { \ int tmp = (a); \ (a) = (b); \ (b) = tmp; \} while (0)// If caller has 'tmp', unexpected behavior occurs:int tmp = 42;SWAP(x, tmp);// Macro's 'tmp' shadows caller's 'tmp'// For general-use macros in headers, use unique names:#define SWAP(a, b) do { \ typeof(a) SWAP\_tmp\_ = (a); \ (a) = (b); \ (b) = SWAP\_tmp\_; \} while (0)// Note: For local macros in .c files with controlled usage,// simple readable names may be acceptable with careful review
General Guidance : Prefer inline functions over function-like macros when possible. Macros bypass type checking and can have surprising behavior with side effects. Use macros only when compile-time computation, token manipulation, or generic programming is essential. When macros are necessary, use them with careful consideration of their limitations.
When converting between casing styles, preserve word boundaries with underscores:
DateTime type → date_time module → DATE_TIME constants (not DATETIME)
buffer_size → BUFFER_SIZE (not BUFFERSIZE)
max_retry_count → MAX_RETRY_COUNT (not MAXRETRYCOUNT)
Acronyms : In PascalCase, keep acronyms capitalized. When converting to other cases, treat the acronym as a single word: [AVMCCS-N002]
CSVParser (PascalCase) → csv_parser (snake_case) → CSV_PARSER (SCREAMING_SNAKE_CASE)
URLHandler → url_handler → URL_HANDLER
IOManager → io_manager → IO_MANAGER
Never split acronyms: C_S_V_PARSER is wrong
This ensures consistency between related identifiers:
// Type: DateTime (PascalCase, no underscores)typedef struct { ... } DateTime;// Module: date\_time (preserves word boundary)int64\_t date\_time\_diff\_seconds(...);// Constants: DATE\_TIME (preserves word boundary)#define DATE\_TIME\_MAX\_YEAR 9999#define DATE\_TIME\_EPOCH 1970
Composite types : Use PascalCase for non-scalar types (structs, unions) [AVMCCS-N003]
Enumeration values : Use PascalCase for enum constants representing exclusive alternatives [AVMCCS-N004]
Manifest constants : All compile-time constants, regardless of definition method [AVMCCS-N005]
Flag constants : Bit flags for bitwise operations [AVMCCS-N006]
Functions : All function names [AVMCCS-N007]
Variables : Local and global variables (including struct fields) [AVMCCS-N008]
Function parameters : All parameter names [AVMCCS-N009]
Type aliases for scalars : When creating scalar type aliases [AVMCCS-N010]
Clear distinction between arrays and other collection types prevents confusion:
// Arrays: Use plural names with matching \_len suffixtypedef struct{int user\_ids[MAX\_USERS];// Plural for arraysize\_t user\_ids\_len;// Length matches array namechar \*names[];// Plural for arraysize\_t names\_len;// Length matches array name} ArrayExample;// Usage is clear:example-\>user\_ids[0] = 42;// Valid: plural indicates arrayexample-\>names[i] = strdup(name); // Valid: plural indicates array// Non-array collections: Use descriptive names with type suffixtypedef struct{List user\_list;// Not "users" - can't index directlyMap name\_to\_id\_map;// Descriptive of contentsSet permission\_set;// Clear it's a setTree category\_tree;// Clear it's a tree} CollectionExample;// Usage makes type obvious:list\_append(&example-\>user\_list, user);// Can't do user\_list[0]map\_get(&example-\>name\_to\_id\_map, "Alice"); // Clearly a map operation// Avoid ambiguous plurals for non-arraystypedef struct{List users;// Bad: looks like array (users[0] seems valid)Set permissions; // Bad: looks like array} BadExample;
Key rules :
Arrays get plural names (users[], paths[]) [AVMCCS-N011]
Array lengths match the array name (users_len, paths_len) [AVMCCS-N012]
Non-array collections get descriptive names with type suffix (user_list, id_set) [AVMCCS-N013]
This prevents confusion about direct indexability
_to / _from : Use for infallible, straightforward type conversions
Requirements :
Avoid _to/_from when :
_is / _has / _can : Return bool and take const pointers
Naming pattern : module_predicate_property
Always : Take const pointers when checking object state
Return : Only bool (use other patterns for error codes)
string : Always means NULL-terminated C string
_buf suffix : Non-NULL-terminated buffer (requires length)
No cstring prefix/suffix : Redundant - strings are C strings by default
Omit string when obvious : parse_iso8601(void) not parse_iso8601_string(void)
Conversely : parse_iso8601_buf() needs _buf suffix when parsing from a buffer
Functions must be prefixed with their logical module name to establish clear namespacing:
// DateTime moduleint64\_t date\_time\_diff\_seconds(const DateTime \*a, const DateTime \*b);bool date\_time\_is\_valid(const DateTime \*dt);// ...// Buffer utilities modulebool buffer\_append(Buffer \*buffer, const char \*data);void buffer\_clear(Buffer \*buffer);// ...
Static functions (file-local) should not use module prefixes since they don’t pollute the global namespace:
// In date\_time.cstatic bool is\_date\_valid(int year, int month, int day);static int days\_in\_month(int month, int year);// Public functions still use module prefixbool date\_time\_is\_valid(const DateTime \*dt){return is\_date\_valid(dt-\>year, dt-\>month, dt-\>day);}
Naming pattern : Consider verb-first naming for static helpers to distinguish from public APIs:
// Public API: module prefix firstbool date\_time\_is\_valid(const DateTime \*dt);// Static helper: verb firststatic bool is\_valid\_date\_time(const DateTime \*dt);static int get\_days\_in\_month(int month, int year);
All macros use SCREAMING_SNAKE_CASE [AVMCCS-N019]:
#define MAX(a, b) ((a) \> (b) ? (a) : (b))#define ARRAY\_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))#define DEFAULT\_BUFFER\_SIZE 1024
Scalar type aliases : Use _t suffix for type definitions representing scalar or opaque types
Composite types : No suffix required (already using PascalCase)
Enum values exist in the global namespace and can cause collisions. Use a common prefix derived from the enum type name to avoid conflicts:
typedef enum{DigitalLow,DigitalHigh,DigitalReadFailed} digital\_read\_result\_t;typedef enum{ConnectionDisconnected,ConnectionConnected,ConnectionAuthenticating,ConnectionAuthenticated} connection\_state\_t;
Naming Convention :
Use singular names for exclusive state enums [AVMCCS-N022]
Use singular + _flags_t suffix for combined flags types [AVMCCS-N023]
// Exclusive states: use singular (only one at a time)typedef enum {FileOpen,FileClosed,FileError} file\_state\_t;// Singular// Combinable flags: enum is singular, flags type is pluraltypedef enum {PERM\_READ= 0x01,PERM\_WRITE = 0x02,PERM\_EXEC= 0x04} permission\_t;// Singular for the enumtypedef uint32\_t permission\_flags\_t;// Singular + flags\_t suffix
Include units in names to prevent errors and improve clarity:
// Functions: unit suffix indicates primary parameter or return value unitsvoid delay\_ms(uint32\_t milliseconds);void set\_timeout\_ms(uint32\_t timeout);// Action on parameterint64\_t get\_timestamp\_us(void);// Return value unitfloat calculate\_distance\_km(Point a, Point b);// Parameters: make units explicitvoid screen\_rotate\_deg(int rotation\_deg);void motor\_set\_speed\_rpm(int speed\_rpm);// No units needed when using enum typesvoid screen\_set\_orientation(screen\_orientation\_t orientation);// Variables and constants: prevent unit confusionconst int MAX\_SPEED\_KMH = 120;float temperature\_celsius = 25.5;uint32\_t file\_size\_bytes = 1024;
Common unit suffixes:
Time: _ms, _us, _ns, _sec, _min
Distance: _mm, _cm, _m, _km
Angles: _deg, _rad
Data: _bytes, _kib, _mib, _gib (use binary prefixes for clarity)
Frequency: _hz, _khz, _mhz
Speed: _rpm, _kmh, _mps
Note : Bytes can be omitted for common memory operations where the unit is obvious:
void \*malloc(size\_t size);// Not malloc\_bytesbuffer\_create(&buf, 1024);// Not buffer\_create\_bytes#define MAX\_BUFFER\_SIZE 4096 // Not MAX\_BUFFER\_SIZE\_BYTES
Use consistent suffixes for counts and sizes [AVMCCS-N025]:
len/length : Number of elements in arrays/buffers
size : Number of bytes (as in sizeof)
count : Number of items in any collection
void process\_items(Item items[], size\_t items\_len);// Not items\_sizevoid \*buffer\_allocate(size\_t buffer\_size);// In bytessize\_t get\_user\_count(const Database \*db);// Number of users
Follow this consistent parameter order for better API clarity:
func(target, inputs, outputs, options, environment)
target : The primary parameter being operated on (must be initialized)
inputs : Additional input data with const qualification
outputs : Usually uninitialized output parameters (come after inputs)
options : Function options (prefer enums for clarity)
environment : Context or environment pointers (avoid globals)
The target parameter must be initialized (except for _init/_create), while output parameters are usually uninitialized:
// acc must be initialized - it's the target parametervoid int32\_add\_parsed(int32\_t \*acc, const char \*str);// out is uninitialized - it's an output parameterparse\_int\_result\_t int32\_parse(const char \*str, int32\_t \*out);
Use enums for function options to create self-documenting APIs:
// Good: Intent is clear at call siteuser\_message\_show(&display, "Hello", UserMessageBlinking);connection\_open(&conn, "example.com", ConnectionSecure);// Bad: Unclear boolean parametersuser\_message\_show(&display, "Hello", true);// What does true mean?connection\_open(&conn, "example.com", false); // Encrypted? Async?
Enums are also more maintainable - adding a third option doesn’t break existing code.
Use return values for status/errors, output parameters for actual results:
// Good: Can check errors inlineif (parse\_int("123", &value) != ParseIntOk) {handle\_error();return;}// value is now safe to use// Follows standard C patternsif (sscanf(input, "%d", &number) != 1) {// Handle parse error}
Consider using function-specific or module-specific result enums for clearer error handling:
// Function-specific result enumtypedef enum{ParseConfigOk,ParseConfigFileOpenFailed,ParseConfigOutOfMemory,ParseConfigInvalidSyntax,ParseConfigMissingRequired} parse\_config\_result\_t;// Usage provides clear, specific error informationparse\_config\_result\_t result = parse\_config("app.conf", &config);switch (result) {case ParseConfigOk:break;case ParseConfigFileOpenFailed:log\_error("Cannot open configuration file");break;case ParseConfigInvalidSyntax:log\_error("Configuration syntax error");break;// ...}
This pattern provides context-specific errors without polluting the global namespace.
Use syntax that communicates intent:
Array syntax for multiple elements: void sort_numbers(int32_t arr[], size_t len)
Pointer syntax for single objects: void increment(int32_t *value)
Pointer syntax for strings: size_t strlen(const char *str)
The [] syntax in parameters documents that multiple items are expected.
Global variables should be avoided. Instead, use a context structure [AVMCCS-A005]:
// Define a context structure for all "global" statetypedef struct{Logger \*logger;Database \*db;Config \*config;int request\_count;bool system\_initialized;} GlobalContext;// Pass the context to functions that need itvoid process\_request(Request \*req, GlobalContext \*ctx){ctx-\>request\_count++;log\_message(ctx-\>logger, "Processing request");database\_query(ctx-\>db, req-\>query);}
This approach provides:
Clear dependencies : Functions explicitly receive context
Easier testing : Can create test contexts
Better thread safety : Each thread can have its own context
No hidden global state : All dependencies are visible
Note: Sometimes globals cannot be avoided (e.g., signal handlers, certain embedded systems constraints), but the context pattern should be the default approach.
Define separate types for flag combinations to avoid semantic issues [AVMCCS-A006]:
// Define individual flags in an enum (for grouping and documentation)typedef enum{FONT\_REGULAR= 0x00,FONT\_BOLD= 0x01,FONT\_ITALIC= 0x02,FONT\_UNDERLINE = 0x04} font\_style\_t;// Define a separate type for combined flagstypedef uint32\_t font\_style\_flags\_t;// Functions take the flags type, not the enum typevoid text\_render(const char \*text, font\_style\_flags\_t style\_flags);// Usage is clear and type-safefont\_style\_flags\_t style = FONT\_BOLD | FONT\_ITALIC;text\_render("Hello", style);
This pattern solves a fundamental type safety issue: when you OR flags together (FONT_BOLD | FONT_ITALIC), the result is not a member of the enum type. By using a separate _flags_t type, we acknowledge this reality while maintaining clarity.
API clearly shows intent :
void file\_set\_state(file\_state\_t state);// One statevoid file\_set\_permissions(permission\_flags\_t perms);// Multiple flags
C23 Enhancement : When C23 is available, specify the enum’s underlying type for perfect compatibility:
// C23: Ensures enum and flags type are compatibletypedef enum font\_style\_t : uint32\_t{FONT\_REGULAR= 0x00,FONT\_BOLD= 0x01,FONT\_ITALIC= 0x02,FONT\_UNDERLINE = 0x04} font\_style\_t;typedef uint32\_t font\_style\_flags\_t;// Guaranteed compatible with enum
Always use pointers for structs (efficiency and API consistency) [AVMCCS-A007]:
// Good: Pass structs by pointervoid date\_time\_add\_days(DateTime \*dt, int days);bool user\_is\_valid(const User \*user);Config \*config\_new(const ConfigOptions \*options);// Bad: Pass by value (inefficient, inconsistent)DateTime date\_time\_add\_days(DateTime dt, int days);bool user\_validate(User user);
If pass-by-value cannot be avoided for any reason, structure should be small (≤ 16 bytes):
// Acceptable only for very small structstypedef struct{uint8\_t r, g, b, a;} Color;// 4 bytes - OK for pass-by-value if necessary// Too large for pass-by-valuetypedef struct{double x, y, z;} Vector3D;// 24 bytes - always use pointers
Use size_t for sizes, lengths, and array indices:
// Good: Correct type for sizessize\_t buffer\_size = 1024;for (size\_t i = 0; i \< array\_len; i++) { ... }// Bad: Using int for sizesint buffer\_size = 1024;// Can't represent large sizes
Warning : Be careful with reverse iteration using size_t:
// Bad: This is an infinite loop!for (size\_t i = len - 1; i \>= 0; i--) { ... }// i is never \< 0// Good: Alternative approachesfor (size\_t i = len; i \> 0; i--) {process(array[i - 1]);}// Good: Use signed type when availablefor (ssize\_t i = len - 1; i \>= 0; i--) { ... }
Use proper types for pointer operations:
// Good: Types designed for pointer operationsuintptr\_t addr = (uintptr\_t) ptr;ptrdiff\_t offset = ptr2 - ptr1;// Bad: Using long for pointers (not portable)long addr = (long) ptr;// Wrong size on some platforms
Use explicit-width types from <stdint.h> for portable size guarantees:
// Good: Clear about size requirementsuint32\_t crc32\_calculate(const uint8\_t \*data, size\_t len);int64\_t timestamp\_microseconds(void);// Bad: Platform-dependent sizesunsigned long flags;// 32 or 64 bits?int counter;// Usually 32 bits, but not guaranteed
Choose the appropriate pointer type based on semantic intent:
void * : Generic memory operations
uint8_t * : Binary data buffers
char * : C strings or character buffers
char8_t * : UTF-8 strings (C23)
Guidelines:
Always use const for read-only parameters [AVMCCS-T005]
Use const for arrays of strings to ensure they’re stored in read-only memory [AVMCCS-T006]
Place const before the type for readability [AVMCCS-T007]
const char \*ptr;// Preferred: pointer to const datachar const \*ptr;// Equivalent but avoid// Const pointer to const dataconst char \*const ptr;// Common patterns:void string\_print(const char \*str);// Can't modify string contentsvoid buffer\_process(const uint8\_t \*data, size\_t len);// Can't modify buffer// Making string arrays fully const for .rodata sectionstatic const char \*const month\_names[] = {"January", "February", "March", "April","May", "June", "July", "August","September", "October", "November", "December"};
Public API types : Use typedef for types exposed in headers [AVMCCS-T008]
Internal types : Use explicit struct/enum/union keywords for implementation details
Function pointers : Always use typedef for readability [AVMCCS-T009]
Use forward declarations to minimize dependencies and hide implementation details:
Opaque types in public headers : Hide struct internals from API users [AVMCCS-T010]
Minimize header dependencies : Forward declare to avoid unnecessary includes [AVMCCS-T011]
Break circular dependencies : Use forward declarations when types reference each other [AVMCCS-T012]
Benefits:
Faster compilation : Fewer headers to parse
Better encapsulation : Implementation details stay private
Stable APIs : Internal changes don’t affect users
Proper structure member ordering improves performance and reduces memory usage:
// Good: Largest to smallest orderingtypedef struct{double value;// 8 bytesint64\_t timestamp;// 8 bytesint32\_t id;// 4 bytesint16\_t flags;// 2 byteschar status;// 1 bytechar type;// 1 byte} Measurement;// 24 bytes, well-packed// Bad: Poor ordering wastes memorytypedef struct{char status;// 1 byte + 7 paddingdouble value;// 8 byteschar type;// 1 byte + 3 paddingint32\_t id;// 4 bytes} WastefulMeasurement;// 24 bytes with 10 bytes wasted
Order structure members from largest to smallest [AVMCCS-M001]
Benefits of proper alignment:
Cache-efficient : Less padding means more data fits in cache lines
Required on some platforms : Misaligned access can crash
Space-efficient : Critical for arrays - saving 8 bytes per struct means 8MB saved per million elements
Use bit fields sparingly for hardware interfaces, wire protocols, and advanced space saving [AVMCCS-M002]:
// Good: Hardware register mappingtypedef struct{uint32\_t enable : 1;uint32\_t mode : 3;uint32\_t priority : 4;uint32\_t reserved : 24;// Explicit padding} ControlRegister;// Good: Network protocoltypedef struct{uint8\_t version : 4;uint8\_t header\_length : 4;} PacketHeader;
Note : These examples assume natural alignment. For packed structures or specific alignment requirements, use compiler-specific attributes.
Rules:
Always use unsigned types for bit fields
Explicitly pad to natural boundaries
Avoid signed bit fields - behavior is confusing
// Bad: Signed bit fieldstruct BadExample{int flag : 1;// Can only hold -1 or 0, not 0 or 1!};
Byte order (endianness): Network and file data may differ from CPU native order. Convert network/file data to host byte order [AVMCCS-M003]
// Network data is big-endian, most CPUs are little-endianuint32\_t network\_value = recv\_from\_network();uint32\_t host\_value = ntohl(network\_value);// Network to host long
Alignment of external data : Direct access to network/file buffers can cause crashes. Use memcpy for potentially unaligned external data [AVMCCS-M004]
// Bad: Direct cast of network buffer (may be misaligned)uint32\_t \*value = (uint32\_t \*) &network\_buffer[offset];// Dangerous!// Good: Use memcpy for potentially unaligned datauint32\_t value;memcpy(&value, &network\_buffer[offset], sizeof(value));value = ntohl(value);// Then convert byte order
Always check allocation failures [AVMCCS-M005]:
// Good: Check and handle failure appropriatelyvoid \*buffer = malloc(size);if (!buffer) {// For embedded systems: propagate errorreturn ERROR\_OUT\_OF\_MEMORY;// For applications: abort may be acceptableabort();// Or log\_fatal("Out of memory");}// Consider calloc for zero-initialized memoryUser \*users = calloc(count, sizeof(User));// Already zeroed
Every allocation must have a corresponding deallocation [AVMCCS-M006]:
// Good: Balanced allocation and deallocationchar \*buffer = malloc(buffer\_size);if (!buffer) {return ERROR\_OUT\_OF\_MEMORY;}// ... use buffer ...free(buffer);// Always free what you malloc// Good: Track ownership carefullytypedef struct{char \*name;// Owned by this structconst char \*ref;// Borrowed reference, don't free} Example;Example \*example\_new(const char \*name, const char \*ref){Example \*ex = malloc(sizeof(Example));if (!ex) {return NULL;}ex-\>name = strdup(name);// We allocate, we must freeex-\>ref = ref;// Borrowed, don't freeif (!ex-\>name) {free(ex);// Clean up on failurereturn NULL;}return ex;}void example\_delete(Example \*ex){if (ex) {free(ex-\>name);// Free what we allocated// Don't free ex-\>ref - we don't own itfree(ex);}}
Key principles:
Every malloc()/calloc()/realloc() must have a matching free()
Document ownership in structs and function documentation
Free in reverse order of allocation when possible
Set pointers to NULL after freeing to avoid use-after-free
Key principles:
Every malloc()/calloc()/realloc() must have a matching free()
Document ownership in structs and function documentation
Free in reverse order of allocation when possible
Set pointers to NULL after freeing to avoid use-after-free
Avoid large stack allocations and variable-length arrays (VLAs):
// Bad: VLAs are dangerousvoid process(int n){int data[n];// Stack overflow risk, no compile-time checks}// Bad: Large stack arraysvoid parse(void){char buffer[8192];// Too large for stack}// Good: Use heap for large or variable sizesvoid process(int n){int \*data = malloc(n \* sizeof(int));if (!data) {// handle error herereturn;}// ...free(data);}
Guidelines:
Limit stack arrays to ~256 bytes [AVMCCS-M007]
Only use big stack arrays in leaf functions (don’t call other functions) [AVMCCS-M008]
Never use VLAs or alloca() - they prevent static stack analysis [AVMCCS-M009]
Prefer fixed-size arrays over VLAs - predictable is better than minimal [AVMCCS-M010]
Memory tips:
free(NULL) is safe - no need to check
Initialize pointers to NULL for clean cleanup paths
malloc(0) is valid but implementation-defined - avoid if possible
C structures often require initialization and cleanup. Use consistent function naming to distinguish between simple initialization, complex resource management, and heap allocation:
_init : Initialize an object in-place (no cleanup needed)// Simple initialization - no cleanup requiredDateTime my\_date;date\_time\_init(&my\_date, 2025, 4, 25);// No cleanup needed - can safely go out of scope
_create : Initialize complex object with internal resources (no malloc)
_destroy : Clean up internal resources (no free)
// Stack allocation with resource managementBuffer buffer;buffer\_create(&buffer, 1024);// Allocates internal buffer// ... use bufferbuffer\_destroy(&buffer);// Frees internal buffer
_new : Allocate and initialize (malloc + create)
_delete : Clean up and deallocate (destroy + free)
// Heap allocation patternUser \*user = user\_new("Alice");// ... use useruser\_delete(user);// Full cleanup and deallocation
Use _init when:
Object requires only simple field initialization
No internal resources are allocated
No corresponding cleanup is needed
Use _create/_destroy when:
Object is already allocated (e.g., on the stack)
Internal resources need allocation/cleanup (buffers, file handles, etc.)
Important : _destroy must be called before the object goes out of scope
Use _new/_delete when:
Dynamic heap allocation is needed or preferred
Working with opaque types where size is not known to the caller
Object lifetime spans multiple scopes
Documentation should enhance code clarity without stating the obvious. Focus on contracts, safety requirements, and non-obvious behavior rather than restating what the code already expresses.
Always document:
All public APIs (minimum: brief description) [AVMCCS-D001]
Parameter relationships (array/length pairs, interdependencies) [AVMCCS-D002]
Memory ownership and lifetime requirements [AVMCCS-D003]
Units of measurement (even when already in parameter names) [AVMCCS-D004]
Preconditions and postconditions [AVMCCS-D005]
Side effects and thread safety [AVMCCS-D006]
Error handling behavior [AVMCCS-D007]
Non-obvious algorithmic complexity [AVMCCS-D008]
Avoid documenting:
Self-evident information (e.g., “increments the counter” for counter_increment())
Implementation details in public API docs
Obvious parameters (e.g., “the string to process” for a str parameter)
[in] direction for any parameters (it’s the default)
Public APIs (in headers):
Use formal documentation system (e.g., Doxygen) [AVMCCS-D009]
Use Javadoc-style comments (/**) for Doxygen [AVMCCS-D010]
Complete parameter documentation with directions [AVMCCS-D011]
Include examples for non-trivial usage [AVMCCS-D012]
Document all error conditions [AVMCCS-D013]
Private APIs (static functions, internal headers):
Use informal comments when clarification needed
Focus on “why” rather than “what”
Document only non-obvious behavior
Keep documentation close to code
// Public API in header - formal documentation/\*\* \* @brief Calculate CRC32 checksum of data buffer \* \* @param data Buffer containing data to checksum \* @param data\_len Number of bytes in data buffer \* @return CRC32 checksum value \*/uint32\_t crc32\_calculate(const uint8\_t data[], size\_t data\_len);// Private API in implementation - informal comment// Uses lookup table for speed - see Sarwate's algorithmstatic uint32\_t crc32\_byte(uint32\_t crc, uint8\_t byte);
Choose documentation depth based on API complexity and safety requirements:
Minimal : Self-documenting functions need only brief description
Standard : Add parameters, return values, and specific error codes [AVMCCS-D014]
Safety-Critical : Add preconditions, warnings, and detailed behavior [AVMCCS-D015]
Parameter Direction : Only annotate output and bidirectional parameters [AVMCCS-D016]
/\*\* \* @brief Parse configuration from string \* \* @param config\_str Configuration string to parse \* @param[in,out] line\_count On input: max lines; on output: lines parsed \* @param[out] result Parsed configuration (undefined on error) \*/config\_parse\_result\_t config\_parse(const char \*config\_str, size\_t \*line\_count,Config \*result);
Array/Length Parameters : Always document the relationship [AVMCCS-D017]
/\*\* \* @brief Hash multiple data buffers \* \* @param[in,out] hash Hash context (must be initialized) \* @param chunks Array of data buffers to hash \* @param sizes Array of buffer sizes (sizes[i] = length of chunks[i]) \* @param count Number of chunks (length of both arrays) \*/void hash\_update\_multiple(Hash \*hash, const uint8\_t \*chunks[],const size\_t sizes[], size\_t count);
Dual-Purpose Parameters : Clearly explain both uses [AVMCCS-D018]
/\*\* \* @brief Decode base64 data \* \* @param encoded Base64 encoded string \* @param[out] decoded Output buffer for decoded data \* @param[in,out] decoded\_len On input: buffer capacity; on output: bytes written \* @warning Output buffer must have capacity for at least (strlen(encoded) \* 3/4) bytes \*/base64\_result\_t base64\_decode(const char \*encoded, uint8\_t decoded[],size\_t \*decoded\_len);
Memory Ownership : Be explicit about allocation and lifetime [AVMCCS-D019]
/\*\* \* @brief Get configuration value \* \* @param config Configuration object \* @param key Configuration key to look up \* @return Pointer to configuration value (borrowed reference) \* @warning Returned pointer valid only while config object exists \* @warning Do not free returned pointer \*/const char \*config\_get(const Config \*config, const char \*key);/\*\* \* @brief Take ownership of allocated buffer \* \* @param[in,out] pool Memory pool \* @param size Buffer size to allocate \* @return Allocated buffer (caller owns memory) \* @post Caller must free returned buffer with memory\_pool\_free() \* @retval NULL Allocation failed \*/void \*memory\_pool\_alloc(MemoryPool \*pool, size\_t size);
Units : Document even when parameter names include units [AVMCCS-D020]
/\*\* \* @brief Set network timeout \* \* @param[in,out] conn Connection object \* @param timeout\_ms Timeout in milliseconds (0 = no timeout) \* @note Actual timeout may be rounded to system timer resolution \*/void connection\_set\_timeout\_ms(Connection \*conn, uint32\_t timeout\_ms);
Macro Documentation : All public macros require documentation [AVMCCS-D021]
/\*\* \* @def MAX\_PATH\_LENGTH \* @brief Maximum path length including null terminator \*/#define MAX\_PATH\_LENGTH 4096/\*\* \* @def ARRAY\_SIZE(arr) \* @brief Calculate number of elements in array \* @param arr Array variable (not pointer) \* @warning Only works with actual arrays, not pointers \*/#define ARRAY\_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
Use comments to explain why, not what. Default to comments above code blocks [AVMCCS-D022]:
// Good: Explains why, not what// Client expects response within 100ms, so we try cheaper cache firstresult = cache\_lookup(key);if (!result) {result = database\_query(key);cache\_store(key, result);}// Bad: States the obvious// Look up in cache, then databaseresult = cache\_lookup(key);if (!result) {result = database\_query(key);}
Use end-of-line comments sparingly, only for emphasis [AVMCCS-D023]:
// Good: Emphasizing critical detaildo1();cache\_invalidate();// Must invalidate after do1, before do2do2();// Bad: Regular comments at end of lineint counter;// Counter variable
Use standardized tags for actionable comments [AVMCCS-D024]:
TODO : Planned improvements or missing features
FIXME : Known bugs that need fixing
HACK : Temporary workarounds that should be improved
WORKAROUND : Permanent workarounds for external issues
// TODO: Add support for IPv6 addresses// FIXME: Buffer overflow when input \> 1024 bytes// HACK: Sleep to avoid race condition (remove after fixing event system)// WORKAROUND: Library bug requires manual byte swapping
Never commit commented-out code without explanation [AVMCCS-D025]:
// Bad: No explanation// old\_function();// more\_old\_code();// Good: Explained reference/\* \* Previous algorithm kept for reference (O(n²) but clearer): \* for (i = 0; i \< n; i++) \* for (j = 0; j \< n; j++) \* process(i, j); \*/
Avoid decorative comment blocks [AVMCCS-D026]:
// Bad: Unnecessary decoration/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \* END OF INCLUDES \* \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/// Good: Simple section marker if needed// === Public API ===
When a source file deviates from these guidelines, document the exception at the file’s beginning [AVMCCS-S001]:
/\* Copyright Header \*//\* STYLE EXCEPTION: This file uses camelCase to maintain compatibility \* with the third-party FooBar API. Function parameter order follows \* FooBar conventions rather than our standard order. \*//\* STYLE EXCEPTION: Legacy code - gradual migration in progress. \* New functions follow the style guide. \*/
Common exceptions:
Third-party API compatibility
Platform-specific conventions
Legacy code under gradual migration
Generated code that cannot be modified
Document exceptions clearly to prevent “fixing” during maintenance.
Headers should follow this organization [AVMCCS-S002]:
/\* Copyright Header \*/#ifndef \_DATE\_TIME\_H\_#define \_DATE\_TIME\_H\_#ifdef \_\_cplusplusextern "C" {#endif/\* System includes (alphabetical) \*/#include \<stdbool.h\>#include \<stdint.h\>/\* Project includes (alphabetical) \*/#include \<mylib/result.h\>/\* Macro definitions \*/#define DATE\_TIME\_MAX\_YEAR 9999#define DATE\_TIME\_MIN\_YEAR 1970/\* Type definitions \*/typedef struct{int year;int month;int day;} DateTime;/\* Function declarations \*/bool date\_time\_is\_valid(const DateTime \*dt);int64\_t date\_time\_diff\_seconds(const DateTime \*a, const DateTime \*b);#ifdef \_\_cplusplus}#endif#endif /\* \_DATE\_TIME\_H\_ \*/
Source files should follow this organization [AVMCCS-S003]:
/\* Copyright Header \*//\* Own header first \*/#include "date\_time.h"/\* System includes (grouped and alphabetical) \*/#include \<stdio.h\>#include \<stdlib.h\>#include \<string.h\>/\* POSIX/platform includes \*/#include \<unistd.h\>/\* Project includes \*/#include \<mylib/logging.h\>#include \<mylib/validation.h\>/\* Private macro definitions \*/#define DAYS\_PER\_WEEK 7#define MONTHS\_PER\_YEAR 12/\* Private type definitions \*/struct ParserState{const char \*input;size\_t position;};/\* Static function declarations (if needed) \*/static bool is\_leap\_year(int year);/\* Static function definitions (helpers first) \*/static bool is\_leap\_year(int year){return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);}/\* Public function definitions (API functions last) \*/bool date\_time\_is\_valid(const DateTime \*dt){if (dt-\>year \< DATE\_TIME\_MIN\_YEAR || dt-\>year \> DATE\_TIME\_MAX\_YEAR) {return false;}// ... validation logicreturn true;}int64\_t date\_time\_diff\_seconds(const DateTime \*a, const DateTime \*b){// ... implementation}
Use traditional include guards for maximum compatibility [AVMCCS-S004]:
#ifndef \_MODULE\_NAME\_H\_#define \_MODULE\_NAME\_H\_/\* content \*/#endif /\* \_MODULE\_NAME\_H\_ \*/
Modern compilers may use #pragma once as an alternative.
Group includes by origin, with alphabetical ordering within each group [AVMCCS-S005]:
Own module header (first in .c files only)
C standard library (<stdio.h>, <string.h>, etc.)
POSIX/system headers (<unistd.h>, <fcntl.h>, etc.)
Third-party libraries (<curl/curl.h>, <sqlite3.h>, etc.)
Project headers (<mylib/...> or "..." for local)
Separate each group with a blank line for clarity.
Header files should match their module names using underscores [AVMCCS-S006]:
Module date_time → header file date_time.h
Not datetime.h or DateTime.h
This maintains consistency with the module naming convention.
Using (void) for zero-parameter functions enables crucial compiler type checking. In pre-C23 versions of C, empty parentheses () create an old-style function declaration that accepts any number of arguments without type checking. This is a dangerous legacy feature that can hide serious bugs:
// Dangerous: compiler won't catch errorsvoid process();// Old K&R style - unspecified parametersprocess(42, "error");// Compiles without warning!// Safe: compiler enforces zero parametersvoid process(void);// Explicit zero parametersprocess(42, "error");// Compiler error: too many arguments
While C23 changes () to mean zero parameters, using (void) ensures compatibility across all modern C versions and makes the intent explicit.
The _t suffix distinguishes scalar type aliases from composite types, making type relationships explicit. This enhances code readability and helps prevent type confusion:
// Clear distinction between scalar and composite typesuser\_id\_t id = 42;// Scalar aliasUser user = {.id = id};// Composite type
While POSIX technically reserves the _t suffix for its own type definitions, this convention has become so widespread in C programming that avoiding it would be counterproductive. The _t suffix clearly distinguishes type aliases from variables and functions, improving code readability. Most modern C projects use this convention without issues, as name collisions with POSIX types are rare in practice when using descriptive, project-specific type names (e.g., rgb_t is unlikely to conflict with any POSIX type).
For projects requiring strict POSIX compliance, consider project-specific prefixes like avm_rgb_t.
This guide recommends continuing with _t for its widespread recognition and clarity benefits.
Module prefixing simulates namespaces in C, reducing naming conflicts and improving code organization. This makes it easy to:
Find all functions related to a module with grep
Avoid naming collisions in large projects
Understand code dependencies at a glance
// Clear module boundariesDateTime start\_time, end\_time;// ...int64\_t elapsed = date\_time\_diff\_seconds(&start\_time, &end\_time);
Using SCREAMING_SNAKE_CASE for all constants provides implementation flexibility - switching between #define and enum definitions is transparent to client code:
// These are interchangeable from a naming perspective#define MAX\_RETRIES 3enum { MAX\_RETRIES = 3 };
PascalCase for composite types creates immediate visual distinction, making it clear when a variable represents a structured data type rather than a scalar value:
DateTime meeting\_time;// Clearly a struct/composite typeint32\_t user\_id;// Clearly a scalar value
The casing distinction between enum values (PascalCase) and flag constants (SCREAMING_SNAKE_CASE) signals different usage patterns:
// PascalCase enums: finite, mutually exclusive - use switch statementsswitch (connection\_state) {case ConnectionDisconnected: /\* handle \*/ break;case ConnectionConnected: /\* handle \*/ break;case ConnectionAuthenticating: /\* handle \*/ break;// Compiler can warn about missing cases}// SCREAMING\_SNAKE\_CASE flags: combinable - use bit operationsfont\_style\_flags\_t style = FONT\_BOLD | FONT\_ITALIC;if (style & FONT\_BOLD) {// Handle bold styling}
The early return pattern reduces cognitive load by handling edge cases first, leaving the main logic unindented and easier to follow. This mirrors how we naturally think about problems - deal with special cases, then focus on the core logic.
Using goto for cleanup provides a structured, predictable pattern that:
Avoids code duplication (single cleanup path)
Handles all error cases consistently
Follows a well-established C idiom
Is more maintainable than nested if-else chains
The context struct pattern provides numerous benefits:
Testability : Easy to create isolated test contexts
Thread safety : Each thread can have its own context
Explicit dependencies : Functions clearly state what they need
No hidden state : All dependencies are visible in function signatures
Distinguishing between enum definitions and flag types solves a fundamental type safety issue in C. When you OR flags together (FONT_BOLD | FONT_ITALIC), the result is not a member of the enum. By using a separate _flags_t type, we acknowledge this reality while maintaining clarity about what values are acceptable.
Well-documented APIs form contracts between implementers and users. Documentation that focuses on preconditions, postconditions, and invariants helps understand safe usage patterns. This is especially critical in C where type systems cannot express ownership, lifetime, or thread safety requirements.
Over-documentation creates maintenance burden and obscures important information. By documenting only what adds value beyond the code itself, we keep documentation synchronized with implementation. Self-documenting code through good naming reduces documentation needs - when buffer_is_empty() clearly returns a boolean indicating emptiness, extensive documentation adds no value.
Using formal documentation only for public APIs creates clear boundaries in generated documentation. This helps users focus on intended interfaces without being distracted by implementation details. Private functions can still have informal comments for maintainers without cluttering public documentation.
This convention system creates self-documenting code where the naming pattern immediately conveys the semantic role of each identifier.
|
Rule
|
Description
|
Example
| | --- | --- | --- | |
Braces
|
K&R variant (new line for functions/types)
|
void f(void)\n{
| |
Indentation
|
4 spaces, no tabs
|
if (x) {
| |
Pointer *
|
With variable name
|
char *name not char* name
| |
Line length
|
< 100 columns
|
Use intermediate variables
| |
Hex numbers
|
Uppercase letters
|
0xDEADBEEF not 0xdeadbeef
| |
Variable declarations
|
One per line (generally)
|
char *first;\nchar *last;
| |
Function spacing
|
One empty line between
|
See AVMCCS-F008
| |
Initializer braces
|
Same line as declaration
|
int values[] = {1, 2, 3};
|
|
Context
|
Rule
|
Good
|
Bad
| | --- | --- | --- | --- | |
Control structures
|
Space after keyword
|
if (x)
|
if(x)
| |
Function calls
|
No space before (
|
func()
|
func ()
| |
Comments
|
Space after marker
|
// text
|
//text
| |
Operators
|
Spaces around binary ops
|
a + b
|
a+b
| |
Braces
|
Space before {
|
if (x) {
|
if (x){
| |
Casts
|
Space after )
|
(int *) ptr
|
(int *)ptr
|
|
Location
|
When to Use
| | --- | --- | |
Before return
|
Functions > 3 lines
| |
Between logical blocks
|
Separate steps in functions
| |
Between cases
|
Switch cases > 3 lines
| |
After guard clauses
|
Separate validation from logic
| |
Control flow blocks
|
Compact for ≤3 lines, spaced for longer
| |
Between functions
|
Exactly one empty line
|
|
Rule
|
Description
|
Example
| | --- | --- | --- | |
Variable declaration
|
Declare at first use (C99+)
|
for (int i = 0; i < n; i++)
| |
Empty parameters
|
Use (void) not ()
|
int get_count(void);
| |
Modern headers
|
Use standard headers
|
<stdint.h>, <stdbool.h>
| |
Declaration order
|
storage, qualifiers, type
|
static const int
| |
Increment preference
|
Post-increment for idiom
|
for (int i = 0; i < n; i++)
| |
Early exit
|
Guard clauses reduce nesting
|
if (!valid) { return; }
| |
Switch preference
|
Use for 3+ comparisons
|
Clearer than if-else chains
| |
Goto usage
|
Only for cleanup patterns
|
goto cleanup;
| |
Static assertions
|
Compile-time validation
|
_Static_assert(sizeof(T) == 24, "msg");
|
|
Element
|
Convention
|
Example
| | --- | --- | --- | |
Composite types
|
PascalCase
|
DateTime, User
| |
Enum types
|
lower_snake_case + _t
|
result_t, connection_state_t
| |
Enum values
|
PascalCase + prefix
|
ConnectionConnected, DigitalReadFailed
| |
Flag enums
|
Singular + _t
|
permission_t, font_style_t
| |
Flag types
|
Singular + _flags_t
|
permission_flags_t, font_style_flags_t
| |
Constants/Flags
|
SCREAMING_SNAKE_CASE
|
MAX_RETRIES, FONT_BOLD
| |
Functions/Variables
|
lower_snake_case
|
date_time_diff, user_id
| |
Scalar type aliases
|
lower_snake_case + _t suffix
|
user_id_t, float_or_int32_t
| |
Function prefixes
|
Module-based
|
date_time_*, buffer_*
| |
Static functions
|
No module prefix
|
validate_date() not date_time_validate_date()
| |
Struct fields
|
lower_snake_case
|
hour_offset not hourOffset
| |
Macros
|
SCREAMING_SNAKE_CASE
|
MAX(), ARRAY_SIZE()
| |
Internal structs
|
PascalCase with struct
|
struct ParserState
|
|
Context
|
Rule
|
Example
| | --- | --- | --- | |
PascalCase
|
Keep capitalized
|
CSVParser, URLHandler
| |
To snake_case
|
Treat as one word
|
CSVParser → csv_parser
| |
To SCREAMING_SNAKE
|
Treat as one word
|
CSVParser → CSV_PARSER
|
|
Element
|
Order
|
Example
| | --- | --- | --- | |
Functions
|
storage, function-spec, return-type
|
static inline void
| |
Variables
|
storage, type-qualifier, type
|
static const int
| |
Constants
|
storage, const, type
|
static const char *const
| |
With restrict
|
After other qualifiers
|
const char *restrict src
| |
With volatile
|
Between storage and type
|
static volatile sig_atomic_t
| |
With _Atomic
|
As type qualifier
|
_Atomic int counter
|
|
Type
|
Usage
|
Naming
| | --- | --- | --- | |
State enums
|
Mutually exclusive states
|
Singular: file_state_t
| |
Flag enum definition
|
Individual flag constants
|
Singular: permission_t
| |
Combined flags type
|
Bitwise combinations
|
Singular + _flags_t: permission_flags_t
|
|
Context
|
Convention
|
Example
| | --- | --- | --- | |
Arrays
|
Plural with []
|
User users[], Node nodes[]
| |
Array lengths
|
Match array name + _len
|
users_len, nodes_len
| |
Non-array collections
|
Type suffix recommended
|
List user_list, Set id_set
| |
Element counts
|
Singular + _count
|
user_count, node_count
| |
Functions returning one
|
Singular
|
find_user(), get_node()
| |
Functions returning many
|
Plural
|
get_users(), find_nodes()
|
|
Pattern
|
Usage
|
Example
| | --- | --- | --- | |
_to / _from
|
Infallible conversions
|
color_to_hsv(), color_from_rgb()
| |
_is / _has / _can
|
Predicates (return bool)
|
date_time_is_valid(), user_has_permission()
| |
_buf suffix
|
Non-NULL-terminated buffers
|
count_spaces_in_buf(buf, buf_len)
| |
Unit suffixes
|
Measurement clarity
|
delay_ms(), rotate_deg()
| |
No string suffix
|
Default is C string
|
parse_iso8601() not parse_iso8601_string()
|
|
Function Pattern
|
Allocation
|
Cleanup Required
|
Usage
| | --- | --- | --- | --- | |
_init
|
Stack-friendly
|
No
|
date_time_init(&obj, 2025, 4, 25)
| |
_create / _destroy
|
Stack with resources
|
Yes
|
buffer_create(&obj, 1024), buffer_destroy(&obj)
| |
_new / _delete
|
Heap-allocated
|
Yes
|
user_new("Alice"), user_delete(obj)
|
|
Type
|
Purpose
|
Example
| | --- | --- | --- | |
void *
|
Generic memory operations
|
malloc(), memcpy()
| |
uint8_t *
|
Binary data buffers
|
hash_compute(), base64_encode()
| |
char *
|
C strings or text buffers
|
strcpy(), utf8_validate()
| |
char8_t *
|
Explicit UTF-8 (C23)
|
utf8_normalize()
|
|
Pattern
|
Meaning
|
Example
| | --- | --- | --- | |
const T *ptr
|
Pointer to const data
|
const char *str
| |
T *const ptr
|
Const pointer
|
char *const buffer
| |
const T *const ptr
|
Const pointer to const data
|
const char *const names[]
|
|
Pattern
|
When to Use
|
Note
| | --- | --- | --- | |
Early exit
|
Reduce nesting
|
Guard clause with braces
| |
Switch
|
Testing one variable for 3+ values
|
More efficient and clear
| |
Empty lines in switch
|
Cases > 3 lines
|
Improves readability
| |
Goto for cleanup
|
Resource management
|
Single cleanup path
|
|
Pattern
|
Description
|
Example
| | --- | --- | --- | |
Parameter order
|
target, inputs, outputs, options, environment
|
parse_csv(data, len, &table, opts, env)
| |
Array + length
|
Arrays always followed by length
|
process(arr, arr_len)
| |
Error handling
|
Return status, output via parameters
|
if (parse_int(str, &val) != ParseIntOk)
| |
Enums over bools
|
Self-documenting options
|
UserMessageBlinking not true
| |
Array syntax
|
[] for multiple items, * for single
|
void sort(int arr[], size_t len)
| |
Global state
|
Use context struct, not globals
|
process(Request *req, GlobalContext *ctx)
| |
Struct parameters
|
Always pass by pointer
|
void update(User *user)
|
|
Purpose
|
Type
|
Example
| | --- | --- | --- | |
Sizes/lengths
|
size_t
|
size_t len = strlen(str)
| |
Pointer as integer
|
uintptr_t
|
uintptr_t addr = (uintptr_t)ptr
| |
Pointer difference
|
ptrdiff_t
|
ptrdiff_t off = p2 - p1
| |
Fixed sizes
|
uint32_t, int64_t
|
uint32_t crc32
| |
Binary data
|
uint8_t *
|
hash_compute(uint8_t data[])
| |
Function pointers
|
Always typedef
|
typedef void (*callback_t)(int)
|
|
Rule
|
Example
| | --- | --- | |
Parenthesize parameters
|
#define SQUARE(x) ((x) * (x))
| |
Parenthesize entire expression
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
| |
Multi-statement pattern
|
#define SWAP(a, b) do { ... } while (0)
| |
Watch for side effects
|
Don’t use with ++ or function calls
| |
Avoid variable shadowing
|
Use unique names in macros
|
|
Topic
|
Rule
|
Note
| | --- | --- | --- | |
Stack arrays
|
~256 bytes max
|
Larger needs heap allocation
| |
VLAs
|
Never use
|
Security risk, prevents analysis
| |
Allocation checks
|
Always check malloc
|
Handle or abort
| |
Allocation pairing
|
Every malloc needs a free
|
Track ownership carefully
| |
Cleanup
|
Use goto pattern
|
Initialize pointers to NULL
| |
free(NULL)
|
Safe, no check needed
|
Simplifies cleanup
| |
Struct alignment
|
Largest to smallest
|
Minimize padding
| |
Bit fields
|
Always use unsigned types
|
Signed bit fields have confusing behavior
|
|
Use Case
|
Example
| | --- | --- | |
Struct size verification
|
_Static_assert(sizeof(T) == 24, "Size changed")
| |
Layout assumptions
|
_Static_assert(offsetof(T, field) == 8, "Layout changed")
| |
Config validation
|
_Static_assert(MAX >= MIN, "Invalid config")
| |
Platform assumptions
|
_Static_assert(sizeof(int) == 4, "Needs 32-bit int")
| |
Enum bounds
|
_Static_assert(StateMax <= 255, "Exceeds uint8_t")
|
|
Module Name
|
Header File
|
Not
| | --- | --- | --- | |
date_time
|
date_time.h
|
datetime.h, DateTime.h
| |
csv_parser
|
csv_parser.h
|
csvparser.h, CSVParser.h
| |
buffer_pool
|
buffer_pool.h
|
BufferPool.h, buffer-pool.h
|
|
Category
|
Suffixes
|
Examples
| | --- | --- | --- | |
Time
|
_ms, _us, _ns, _sec, _min
|
delay_ms(100), timeout_sec
| |
Distance
|
_mm, _cm, _m, _km
|
distance_km, gap_mm
| |
Angles
|
_deg, _rad
|
rotate_deg(90), angle_rad
| |
Data
|
_bytes, _kib, _mib, _gib
|
buffer_size_bytes, cache_mib
| |
Frequency
|
_hz, _khz, _mhz
|
sample_rate_khz, clock_mhz
| |
Speed
|
_rpm, _kmh, _mps
|
motor_rpm, velocity_mps
|
|
Pattern
|
Usage
|
Example
| | --- | --- | --- | |
Public API types
|
Use typedef in headers
|
typedef struct { ... } DateTime;
| |
Internal types
|
Use explicit struct keyword
|
struct ParserState state;
| |
Function pointers
|
Always use typedef
|
typedef void (*handler_t)(Event *);
| |
Opaque types
|
Forward declare in headers
|
typedef struct Buffer Buffer;
| |
Circular dependencies
|
Use forward declarations
|
typedef struct Node Node;
|
|
Issue
|
Solution
|
Example
| | --- | --- | --- | |
Byte order
|
Convert to host order
|
value = ntohl(network_value)
| |
Alignment
|
Use memcpy for safety
|
memcpy(&val, &buffer[off], 4)
|
|
Tag
|
Usage
|
Example
| | --- | --- | --- | |
TODO
|
Planned improvements
|
// TODO: Add IPv6 support
| |
FIXME
|
Known bugs
|
// FIXME: Buffer overflow risk
| |
HACK
|
Temporary workarounds
|
// HACK: Sleep to avoid race
| |
WORKAROUND
|
Permanent fixes for external issues
|
// WORKAROUND: Library bug #123
|
|
What to Document
|
When
|
Level of Detail
| | --- | --- | --- | |
All public APIs
|
Always
|
Minimum: @brief description
| |
Parameter directions
|
Output/bidirectional pointers
|
@param[out] or [in,out]
| |
Array/length relationships
|
Always
|
Explicit connection in @param
| |
Memory ownership
|
When not obvious
|
Who allocates, who frees
| |
Lifetime requirements
|
Borrowed pointers
|
How long reference is valid
| |
Units
|
Always
|
Even if in parameter name
| |
Error behavior
|
Non-trivial functions
|
What happens to outputs on error
| |
Preconditions
|
When assumptions exist
|
@pre conditions
| |
Side effects
|
Always
|
Threading, global state, etc.
| |
Complexity
|
When not O(1) or O(n)
|
Big-O notation
|
|
What NOT to Document
|
Example
| | --- | --- | |
Obvious parameters
|
@param str the string
| |
Self-evident behavior
|
increments the counter for counter_increment()
| |
Implementation details
|
Internal algorithms in public API docs
| |
Input direction [in]
|
@param[in] str redundant (it’s the default)
|
|
Documentation Patterns
|
Usage
| | --- | --- | |
Minimal (brief only)
|
Simple predicates, getters
| |
Standard (+params, return)
|
Most public functions
| |
Safety-critical (+pre/post/warning)
|
Functions with preconditions
| |
Complete (+examples, see also)
|
Complex APIs, templates for users
|
Header files (.h):
Copyright header
Include guard (#ifndef)
C++ guard (#ifdef __cplusplus)
System includes (alphabetical)
Project includes (alphabetical)
Macro definitions
Type definitions
Function declarations
C++ guard close
Include guard close (#endif)
Source files (.c):
Copyright header
Own header include
System includes (alphabetical)
POSIX/platform includes
Project includes
Private macro definitions
Private type definitions
Static function declarations
Static function definitions
Public function definitions
This style guide can be largely enforced using clang-format. Create a .clang-format file in your project root:
# AtomVM C Coding Style Guide - clang-format configuration# Place this in .clang-format at your project rootBasedOnStyle: LLVM# Indentation (rules: AVMCCS-F003)IndentWidth: 4TabWidth: 4UseTab: Never# Line length (rules: AVMCCS-F006)ColumnLimit: 100# Brace placement (rules: AVMCCS-F001, AVMCCS-F002)BreakBeforeBraces: CustomBraceWrapping:AfterFunction: trueAfterStruct: trueAfterEnum: trueAfterUnion: trueAfterControlStatement: falseBeforeElse: falseBeforeWhile: false# Use braces for single-statement blocks (rule: AVMCCS-F002)InsertBraces: true# Spacing (rules: AVMCCS-F004, AVMCCS-F015, AVMCCS-F016, AVMCCS-F018, AVMCCS-F020)PointerAlignment: Right# AVMCCS-F004: \* with variableSpaceAfterCStyleCast: true# AVMCCS-F018: Space after castSpacesInCStyleCastParentheses: falseSpaceBeforeParens: ControlStatements# AVMCCS-F015: Space after keywordsSpaceBeforeAssignmentOperators: true# AVMCCS-F020: Spaces around operatorsSpaceInEmptyParentheses: false# AVMCCS-F016: No space in function calls# Line breaking (rules: AVMCCS-F002)AllowShortFunctionsOnASingleLine: NoneAllowShortIfStatementsOnASingleLine: NeverAllowShortLoopsOnASingleLine: falseAlwaysBreakAfterReturnType: NoneBreakBeforeBinaryOperators: None# Other formatting (rules: AVMCCS-F008, AVMCCS-F017)MaxEmptyLinesToKeep: 1# AVMCCS-F008: One empty line between functionsSpacesBeforeTrailingComments: 1# AVMCCS-F017: Space after //# Include ordering (rules: AVMCCS-S005)SortIncludes: CaseSensitiveIncludeBlocks: PreserveIncludeCategories:- Regex:'^".\*\.h"'# Own headersPriority:1- Regex:'^\<(assert|ctype|errno|float|limits|locale|math|setjmp|signal|stdarg|stddef|stdio|stdlib|string|time)\.h\>'Priority:2# C standard library- Regex:'^\<(aio|arpa/|cpio|dirent|dlfcn|fcntl|fmtmsg|fnmatch|ftw|glob|grp|iconv|langinfo|libgen|monetary|mqueue|ndbm|net/|netdb|netinet/|nl\_types|poll|pthread|pwd|regex|sched|search|semaphore|spawn|strings|stropts|sys/|syslog|tar|termios|trace|ulimit|unistd|utime|utmpx|wordexp)\.h\>'Priority:3# POSIX headers- Regex:'^\<.\*\>'Priority:4# Third-party libraries- Regex:'.\*'Priority:5# Project headers
Usage :
# Format a single fileclang-format -i source.c# Format all C files in projectfind . -name "\*.c" -o -name "\*.h" | xargs clang-format -i# Check formatting without modifyingclang-format --dry-run --Werror source.c
clang-format cannot enforce many aspects of this style guide:
Not enforced by clang-format:
Naming conventions (functions, variables, types) - use clang-tidy instead
Header file organization and include grouping semantics beyond alphabetical sorting
Function parameter ordering patterns (AVMCCS-A001)
Semantic spacing (empty lines between logical blocks within functions - AVMCCS-F012)
Empty lines before return statements (AVMCCS-F011)
Comment quality or presence
Static assertion placement
Memory management patterns
API design patterns (enums vs bools)
Declaration order semantics (AVMCCS-L004)
Array vs pointer syntax choices (AVMCCS-A004)
Hexadecimal letter case (AVMCCS-F019) - clang-format preserves existing case
Initializer brace placement (AVMCCS-F021) - limited control available
These require additional tools:
clang-tidy : For naming conventions and deeper analysis
Code review : For semantic patterns and API design
Custom linters : For project-specific rules
Developer discipline : For consistency
To enforce naming conventions and additional checks, create .clang-tidy:
# AtomVM C Coding Style Guide - clang-tidy configuration# Enable only specific checks (avoid using wildcards)Checks: \>-\*,readability-identifier-naming,readability-isolate-declaration,readability-else-after-return,readability-braces-around-statements,misc-definitions-in-headers,bugprone-macro-parenthesesCheckOptions:# Naming conventions (rules: AVMCCS-N003, AVMCCS-N004, AVMCCS-N005, AVMCCS-N007, AVMCCS-N008, AVMCCS-N009, AVMCCS-N019, AVMCCS-N020)- key: readability-identifier-naming.FunctionCasevalue: lower\_case- key: readability-identifier-naming.VariableCasevalue: lower\_case- key: readability-identifier-naming.ParameterCasevalue: lower\_case- key: readability-identifier-naming.GlobalVariableCasevalue: lower\_case- key: readability-identifier-naming.LocalVariableCasevalue: lower\_case- key: readability-identifier-naming.StructCasevalue: CamelCase- key: readability-identifier-naming.UnionCasevalue: CamelCase- key: readability-identifier-naming.EnumCasevalue: lower\_case- key: readability-identifier-naming.EnumConstantCasevalue: CamelCase- key: readability-identifier-naming.MacroDefinitionCasevalue: UPPER\_CASE- key: readability-identifier-naming.TypedefCasevalue: lower\_case- key: readability-identifier-naming.TypedefSuffixvalue: '\_t'# One variable per declaration (rule: AVMCCS-F005)- key: readability-isolate-declaration.Enabledvalue: true# Early return pattern (rule: AVMCCS-L009)- key: readability-else-after-return.WarnOnUnfixablevalue: false# Always use braces (rule: AVMCCS-F002)- key: readability-braces-around-statements.ShortStatementLinesvalue: '0'
Usage :
# Run clang-tidy on a fileclang-tidy source.c -- -I./include# Run on entire project with compile\_commands.jsonclang-tidy -p build/ source.c
clang-tidy cannot enforce all AVMCCS rules:
Not enforced by clang-tidy:
Empty parameter lists using (void) (AVMCCS-L002) - no C-specific check available
Specific module prefixing patterns (AVMCCS-N017, AVMCCS-N018)
Complex naming patterns like array pluralization (AVMCCS-N011, AVMCCS-N012)
API parameter ordering (AVMCCS-A001)
Context structure usage over globals (AVMCCS-A005)
Memory allocation size limits (AVMCCS-M007, AVMCCS-M008)
Goto cleanup patterns (AVMCCS-L011)
Documentation requirements (AVMCCS-D001 through AVMCCS-D026)
File structure organization (AVMCCS-S002, AVMCCS-S003)
Limited enforcement:
Some checks are C++-oriented and may produce false positives for C code
Macro safety (AVMCCS-L015-L017) is only partially checked by bugprone-macro-parentheses
Memory management patterns require multiple checks that may not cover all cases
Most modern editors support clang-format integration:
VS Code : Install the C/C++ extension, format on save
Vim : Use vim-clang-format plugin
Emacs : Use clang-format.el
CLion : Built-in support under Code Style settings
Add a pre-commit hook to ensure consistent formatting:
#!/bin/sh# .git/hooks/pre-commit# Format staged C filesfor file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(c|h)$'); doclang-format -i "$file"git add "$file"done
Consider using additional tools alongside formatting:
cppcheck : For additional static analysis
include-what-you-use : To maintain clean includes
Custom scripts : For project-specific naming patterns and conventions
This appendix provides Doxygen documentation examples using Javadoc-style comments (/**). All examples follow standard Doxygen syntax without custom extensions.
/\*\* \* @brief Check if date/time is valid \* \* @param dt DateTime structure to validate \* @return true if all fields contain valid values, false otherwise \*/bool date\_time\_is\_valid(const DateTime \*dt);/\*\* \* @brief Test if user has specific permission \* \* @param user User object to check \* @param perm Permission flag to test \* @return true if user has permission, false otherwise \*/bool user\_has\_permission(const User \*user, permission\_t perm);
/\*\* \* @brief Parse ISO 8601 date/time from buffer \* \* Parses timestamps in RFC 3339 format from a non-null-terminated buffer. \* Supports fractional seconds up to microsecond precision and timezone \* offsets. The buffer may contain additional data after the timestamp. \* \* @param buf Buffer containing ISO 8601 formatted string \* @param buf\_len Length of buffer in bytes \* @param[out] dt Parsed DateTime structure (undefined on error) \* \* @return Parsing result code \* @retval ParseIso8601Ok Successfully parsed complete timestamp \* @retval ParseIso8601InvalidFormat Format does not match ISO 8601 \* @retval ParseIso8601InvalidDate Date values out of valid range \* @retval ParseIso8601BufferTooSmall Buffer smaller than minimum timestamp \* \* @note Accepts formats: "2025-04-25T10:30:00Z", "2025-04-25T10:30:00.123456Z", \* "2025-04-25T10:30:00+05:30", "2025-04-25T10:30:00.123-08:00" \* \* @code \* // Parse timestamp from network packet \* DateTime dt; \* parse\_iso8601\_result\_t result = date\_time\_parse\_iso8601\_buf(packet-\>data, \* packet-\>data\_len, &dt); \* if (result != ParseIso8601Ok) { \* log\_error("Invalid timestamp: %s", \* parse\_iso8601\_result\_string(result)); \* return PROTOCOL\_ERROR; \* } \* // dt now contains parsed timestamp \* @endcode \* \* @see date\_time\_parse\_iso8601() for null-terminated string variant \* @see date\_time\_format\_iso8601() for formatting timestamps \*/parse\_iso8601\_result\_t date\_time\_parse\_iso8601\_buf(const char buf[], size\_t buf\_len,DateTime \*dt);
/\*\* \* @brief Create hash context for incremental hashing \* \* Allocates and initializes a new hash context for the specified algorithm. \* The context allows incremental hashing of data through multiple update calls. \* \* @param algorithm Hashing algorithm to use \* @param options Algorithm-specific options (may be NULL for defaults) \* \* @return Newly allocated hash context \* @retval NULL Allocation failed or unsupported algorithm \* \* @post Caller must free context with hash\_delete() \* @post Context is initialized and ready for hash\_update() calls \* \* @warning Do not use context after hash\_finalize() without reset \* @warning Context is not thread-safe; use one context per thread \* \* @code \* // Hash a large file in chunks \* Hash \*hash = hash\_new(HashSHA256, NULL); \* if (!hash) { \* return ERROR\_OUT\_OF\_MEMORY; \* } \* \* uint8\_t buffer[4096]; \* size\_t bytes\_read; \* while ((bytes\_read = fread(buffer, 1, sizeof(buffer), file)) \> 0) { \* hash\_update(hash, buffer, bytes\_read); \* } \* \* uint8\_t result[HASH\_SHA256\_SIZE]; \* hash\_finalize(hash, result); \* hash\_delete(hash); \* @endcode \* \* @see hash\_update() to add data to hash \* @see hash\_finalize() to compute final hash value \* @see hash\_reset() to reuse context for new hash \* @since v2.0 \*/Hash \*hash\_new(hash\_algorithm\_t algorithm, const HashOptions \*options);/\*\* \* @brief Update hash with additional data \* \* Processes additional data through the hash algorithm. Can be called \* multiple times to hash data incrementally. \* \* @param[in,out] hash Hash context (must be initialized) \* @param data Data buffer to hash \* @param data\_len Number of bytes to hash from data buffer \* \* @pre hash != NULL && hash\_is\_valid(hash) \* @pre data != NULL || data\_len == 0 \* @pre hash\_finalize() not yet called on this context \* \* @note Zero-length updates are valid but do not change hash state \* @warning Data is processed immediately; buffer can be reused after return \*/void hash\_update(Hash \*hash, const uint8\_t data[], size\_t data\_len);/\*\* \* @brief Finalize hash calculation and retrieve result \* \* Completes the hash calculation and writes the result to the output buffer. \* After finalization, the context cannot accept more data without reset. \* \* @param[in,out] hash Hash context with data to finalize \* @param[out] result Output buffer for hash result \* \* @pre hash != NULL && hash\_is\_valid(hash) \* @pre result != NULL && has sufficient size for algorithm \* @post Context is finalized; hash\_update() will fail until reset \* @post result contains computed hash value \* \* @warning Output buffer must be at least hash\_size(hash-\>algorithm) bytes \* @note Context remains allocated; use hash\_delete() to free \* \* @code \* uint8\_t sha256\_hash[HASH\_SHA256\_SIZE]; \* hash\_finalize(hash, sha256\_hash); \* // sha256\_hash now contains the computed SHA-256 hash \* @endcode \*/void hash\_finalize(Hash \*hash, uint8\_t result[]);
/\*\* \* @brief Allocate buffer from memory pool \* \* Allocates a buffer of the requested size from the pool. The allocation \* is tracked by the pool and must be freed using memory\_pool\_free() or released \* when the entire pool is destroyed. \* \* @param[in,out] pool Memory pool to allocate from \* @param size Number of bytes to allocate \* @param alignment Required alignment (must be power of 2) \* \* @return Pointer to allocated memory \* @retval NULL Allocation failed (pool exhausted or size too large) \* \* @pre pool != NULL && memory\_pool\_is\_valid(pool) \* @pre alignment \> 0 && (alignment & (alignment - 1)) == 0 \* @post Returned memory is aligned to requested boundary \* @post Returned memory is uninitialized \* \* @warning Memory becomes invalid when pool is destroyed \* @warning Do not use standard free() on returned pointer \* \* @note Consider memory\_pool\_calloc() for zero-initialized memory \* @note Pool may allocate more than requested for alignment \* \* @code \* // Allocate aligned buffer for SIMD operations \* float \*vectors = memory\_pool\_alloc\_aligned(pool, 100 \* sizeof(float), 16); \* if (!vectors) { \* return ERROR\_OUT\_OF\_MEMORY; \* } \* // vectors is now 16-byte aligned for SSE operations \* @endcode \* \* @see memory\_pool\_free() to release individual allocations \* @see memory\_pool\_destroy() to release entire pool \*/void \*memory\_pool\_alloc\_aligned(MemoryPool \*pool, size\_t size, size\_t alignment);
/\*\* \* @brief Sort array of integers in-place \* \* Uses introsort algorithm for O(n log n) worst-case performance. \* \* @param[in,out] values Array of integers to sort \* @param values\_len Number of elements in values array \* @param compare Comparison function (NULL for ascending order) \* \* @pre values != NULL || values\_len == 0 \* @post values[0] \<= values[1] \<= ... \<= values[values\_len-1] (for default compare) \* \* @code \* int numbers[] = {5, 2, 8, 1, 9}; \* int\_array\_sort(numbers, ARRAY\_SIZE(numbers), NULL); \* // numbers is now {1, 2, 5, 8, 9} \* \* // Custom comparison for descending order \* int compare\_desc(const void \*a, const void \*b) \* { \* return \*(const int \*)b - \*(const int \*)a; \* } \* int\_array\_sort(numbers, ARRAY\_SIZE(numbers), compare\_desc); \* // numbers is now {9, 8, 5, 2, 1} \* @endcode \*/void int\_array\_sort(int values[], size\_t values\_len,int (\*compare)(const void \*, const void \*));/\*\* \* @brief Find all occurrences of pattern in text \* \* Searches for all non-overlapping occurrences of the pattern within \* the text and stores their positions in the output array. \* \* @param text Text to search within \* @param text\_len Length of text in bytes \* @param pattern Pattern to search for \* @param pattern\_len Length of pattern in bytes \* @param[out] positions Array to store match positions \* @param positions\_capacity Maximum matches to store \* @param[out] match\_count Number of matches found (may exceed capacity) \* \* @return Search result status \* @retval TextSearchOk All matches found and stored \* @retval TextSearchTruncated More matches exist than positions\_capacity \* @retval TextSearchInvalidInput NULL pointers or zero lengths \* \* @note positions array contains byte offsets from start of text \* @note match\_count is set even if positions array is too small \* \* @code \* const char \*text = "the quick the brown the fox"; \* const char \*pattern = "the"; \* size\_t positions[10]; \* size\_t count; \* \* text\_search\_result\_t result = text\_find\_all\_occurrences( \* text, strlen(text), \* pattern, strlen(pattern), \* positions, ARRAY\_SIZE(positions), \* &count \* ); \* \* // positions[0] = 0, positions[1] = 10, positions[2] = 20 \* // count = 3 \* @endcode \*/text\_search\_result\_t text\_find\_all\_occurrences(const char text[], size\_t text\_len,const char pattern[], size\_t pattern\_len,size\_t positions[], size\_t positions\_capacity,size\_t \*match\_count);
/\*\* \* @def MIN(a, b) \* @brief Return minimum of two values \* \* @param a First value \* @param b Second value \* @return The smaller of a and b \* \* @warning Arguments are evaluated twice; avoid side effects \* @note Type-generic; works with any comparable types \* \* @code \* int x = MIN(10, 20); // x = 10 \* float y = MIN(3.14f, 2.71f); // y = 2.71f \* \* // WARNING: Don't do this (undefined behavior) \* int z = MIN(i++, j++); // Multiple evaluation! \* @endcode \*/#define MIN(a, b) ((a) \< (b) ? (a) : (b))/\*\* \* @def CONTAINER\_OF(ptr, type, member) \* @brief Get pointer to containing structure from member pointer \* \* @param ptr Pointer to the member \* @param type Type of the containing structure \* @param member Name of the member within the structure \* @return Pointer to the containing structure \* \* @warning ptr must actually point to member within a type structure \* @warning Undefined behavior if ptr is not from specified member \* \* @code \* struct Person \* { \* char name[50]; \* int age; \* struct Node node; // For linked list \* }; \* \* void process\_node(struct Node \*n) \* { \* struct Person \*person = CONTAINER\_OF(n, struct Person, node); \* printf("Person: %s, age %d\n", person-\>name, person-\>age); \* } \* @endcode \*/#define CONTAINER\_OF(ptr, type, member) \ ((type \*)((char \*)(ptr) - offsetof(type, member)))
/\*\* \* @file memory\_pool.h \* @brief Fast memory pool allocator for fixed-lifetime allocations \* \* This module provides a memory pool allocator optimized for scenarios where \* many allocations share the same lifetime. All allocations from a pool can \* be freed simultaneously by destroying the pool. \* \* @note Thread-safety: Pools are not thread-safe. Use one pool per thread \* or add external synchronization. \* \* @warning Never mix memory\_pool\_free() with standard free() calls \* \* Example usage: \* @code \* MemoryPool \*pool = memory\_pool\_new(64 \* 1024); // 64KB pool \* \* // Allocate from pool - no individual free needed \* User \*user = memory\_pool\_alloc(pool, sizeof(User)); \* user-\>name = memory\_pool\_strdup(pool, "Alice"); \* user-\>data = memory\_pool\_alloc(pool, user-\>data\_size); \* \* // Process user... \* \* // Free everything at once \* memory\_pool\_delete(pool); \* @endcode \* \* @see memory\_pool\_new() to create a pool \* @see memory\_pool\_alloc() for basic allocation \* @see memory\_pool\_alloc\_aligned() for aligned allocation \*/
/\*\* \* @brief Open configuration file \* \* Opens and parses a configuration file, returning a configuration object. \* The file must be in valid INI format with UTF-8 encoding. \* \* @param path Path to configuration file \* @param[out] config Newly created configuration object \* @param[out] error Detailed error information (optional, may be NULL) \* \* @return Status code indicating success or failure \* @retval ConfigOk Successfully loaded configuration \* @retval ConfigFileNotFound Specified file does not exist \* @retval ConfigAccessDenied Insufficient permissions to read file \* @retval ConfigInvalidFormat File is not valid INI format \* @retval ConfigOutOfMemory Memory allocation failed \* \* @post On success, \*config is valid and must be freed with config\_delete() \* @post On failure, \*config is NULL and \*error contains details (if provided) \* \* @code \* Config \*config; \* ConfigError error; \* \* config\_result\_t result = config\_open("app.ini", &config, &error); \* if (result != ConfigOk) { \* fprintf(stderr, "Failed to load config: %s (line %d: %s)\n", \* config\_result\_string(result), \* error.line\_number, \* error.details); \* return EXIT\_FAILURE; \* } \* \* // Use configuration \* const char \*db\_path = config\_get(config, "database", "path"); \* \* config\_delete(config); \* @endcode \* \* @note Error details are only populated on parsing errors \* @see config\_get() to retrieve configuration values \* @see config\_set() to modify configuration \* @see config\_save() to write configuration back to file \*/config\_result\_t config\_open(const char \*path, Config \*\*config,ConfigError \*error);
This section provides a complete index of all rules defined in this style guide, organized by category.
|
ID
|
Rule
|
Section
| | --- | --- | --- | |
Language Constructs
|
|
| |
AVMCCS-L001
|
Declare variables where needed, not just at function start
|
Modern C Practices
| |
AVMCCS-L002
|
Use (void) for empty parameter lists
|
Modern C Practices
| |
AVMCCS-L003
|
Use modern headers (<stdint.h>, <stdbool.h>, etc.)
|
Modern C Practices
| |
AVMCCS-L004
|
Follow consistent order for declaration specifiers
|
Declaration Order
| |
AVMCCS-L005
|
Use restrict for pointer parameters that don’t alias
|
Declaration Order
| |
AVMCCS-L006
|
Use _Atomic for atomic operations (C11)
|
Declaration Order
| |
AVMCCS-L007
|
Use volatile for hardware registers or signal handlers
|
Declaration Order
| |
AVMCCS-L008
|
Prefer post-increment (i++) over pre-increment (++i)
|
Increment/Decrement Usage
| |
AVMCCS-L009
|
Use guard clauses (early exit) to reduce indentation
|
Early Exit Over Nesting
| |
AVMCCS-L010
|
Use switch when testing one variable for 3+ values
|
Switch Over If-Else Chains
| |
AVMCCS-L011
|
Use goto for clean error handling and resource cleanup
|
Goto for Cleanup
| |
AVMCCS-L012
|
Use _Static_assert after type definitions to verify layout assumptions
|
Static Assertions
| |
AVMCCS-L013
|
Use _Static_assert in implementation files to verify platform assumptions
|
Static Assertions
| |
AVMCCS-L014
|
Use _Static_assert to ensure configuration constants are consistent
|
Static Assertions
| |
AVMCCS-L015
|
Always parenthesize macro parameters
|
Macro Safety
| |
AVMCCS-L016
|
Parenthesize the entire macro expression
|
Macro Safety
| |
AVMCCS-L017
|
Use do { ... } while (0) for multi-statement macros
|
Macro Safety
| |
AVMCCS-L018
|
Avoid expressions with side effects as macro arguments
|
Macro Safety
| |
AVMCCS-L019
|
Protect against variable shadowing in complex macros
|
Macro Safety
| |
Formatting
|
|
| |
AVMCCS-F001
|
K&R style variant for brace placement
|
Style Rules
| |
AVMCCS-F002
|
Always use braces, even for single-statement blocks
|
Style Rules
| |
AVMCCS-F003
|
Use 4 space indentation (no tabs)
|
Style Rules
| |
AVMCCS-F004
|
The * belongs with the variable name, not the type
|
General Rules
| |
AVMCCS-F005
|
Generally declare one variable per line
|
General Rules
| |
AVMCCS-F006
|
Keep lines below 100 columns
|
General Rules
| |
AVMCCS-F007
|
Use intermediate variables with meaningful names to break up complex expressions
|
General Rules
| |
AVMCCS-F008
|
Separate function definitions with exactly one empty line
|
General Rules
| |
AVMCCS-F009
|
Lines must not end with spaces or tabs
|
Whitespace Rules
| |
AVMCCS-F010
|
All source files must end with a newline character
|
Whitespace Rules
| |
AVMCCS-F011
|
Separate return statements with empty line when function body > 3 lines
|
Empty Lines and Spacing
| |
AVMCCS-F012
|
Use empty lines to separate logical blocks within functions
|
Empty Lines and Spacing
| |
AVMCCS-F013
|
Compact style for short blocks, spaced for longer ones
|
Empty Lines and Spacing
| |
AVMCCS-F014
|
Separate switch cases with empty lines when > 3 lines per case
|
Empty Lines and Spacing
| |
AVMCCS-F015
|
Space after keywords, before opening brace
|
Spacing
| |
AVMCCS-F016
|
No space between function name and parentheses
|
Spacing
| |
AVMCCS-F017
|
Space after comment markers
|
Spacing
| |
AVMCCS-F018
|
Space after closing parenthesis in casts
|
Spacing
| |
AVMCCS-F019
|
Use uppercase letters for hexadecimal numbers
|
Spacing
| |
AVMCCS-F020
|
Use spaces around binary operators
|
Spacing
| |
AVMCCS-F021
|
Always place initializer braces on the same line as declaration
|
Spacing
| |
Naming
|
|
| |
AVMCCS-N001
|
Preserve word boundaries when converting between casing styles
|
Word Boundary Preservation
| |
AVMCCS-N002
|
Keep acronyms capitalized in PascalCase; treat as single word when converting
|
Word Boundary Preservation
| |
AVMCCS-N003
|
Use PascalCase for composite types
|
PascalCase
| |
AVMCCS-N004
|
Use PascalCase for enum constants representing exclusive alternatives
|
PascalCase
| |
AVMCCS-N005
|
Use SCREAMING_SNAKE_CASE for manifest constants
|
SCREAMING_SNAKE_CASE
| |
AVMCCS-N006
|
Use SCREAMING_SNAKE_CASE for bit flags
|
SCREAMING_SNAKE_CASE
| |
AVMCCS-N007
|
Use lower_snake_case for all function names
|
lower_snake_case
| |
AVMCCS-N008
|
Use lower_snake_case for local and global variables
|
lower_snake_case
| |
AVMCCS-N009
|
Use lower_snake_case for all parameter names
|
lower_snake_case
| |
AVMCCS-N010
|
Use lower_snake_case for scalar type aliases
|
lower_snake_case
| |
AVMCCS-N011
|
Arrays get plural names
|
Arrays and Collections
| |
AVMCCS-N012
|
Array lengths match the array name
|
Arrays and Collections
| |
AVMCCS-N013
|
Non-array collections get descriptive names with type suffix
|
Arrays and Collections
| |
AVMCCS-N014
|
Use _to/_from for infallible, straightforward type conversions
|
Conversion Functions
| |
AVMCCS-N015
|
Use _is/_has/_can for predicates returning bool
|
Predicate Functions
| |
AVMCCS-N016
|
Use _buf suffix for non-NULL-terminated buffers
|
String and Buffer Naming
| |
AVMCCS-N017
|
Functions must be prefixed with their logical module name
|
Module Prefixing
| |
AVMCCS-N018
|
Static functions should not use module prefixes
|
Static Functions
| |
AVMCCS-N019
|
All macros use SCREAMING_SNAKE_CASE
|
Macros
| |
AVMCCS-N020
|
Use _t suffix for scalar type aliases
|
Type Suffix Convention
| |
AVMCCS-N021
|
Use common prefix for enum values to avoid namespace collisions
|
Enumeration Namespace Management
| |
AVMCCS-N022
|
Use singular names for exclusive state enums
|
Enum and Flag Naming
| |
AVMCCS-N023
|
Use singular + _flags_t suffix for combined flags types
|
Enum and Flag Naming
| |
AVMCCS-N024
|
Include units in names to prevent errors
|
Measurement Units
| |
AVMCCS-N025
|
Use consistent suffixes for counts and sizes
|
Size Terminology
| |
API Design
|
|
| |
AVMCCS-A001
|
Follow consistent parameter order (target, inputs, outputs, options, environment)
|
Function Parameter Ordering
| |
AVMCCS-A002
|
Use enums for function options to create self-documenting APIs
|
Prefer Enums Over Bools
| |
AVMCCS-A003
|
Use return values for status/errors, output parameters for results
|
Return Values for Error Handling
| |
AVMCCS-A004
|
Use array syntax [] for multiple elements, pointer syntax * for single objects
|
Array vs Pointer Syntax
| |
AVMCCS-A005
|
Use context structure instead of global variables
|
Global State Management
| |
AVMCCS-A006
|
Define separate types for flag combinations to avoid semantic issues
|
Flag Type Design
| |
AVMCCS-A007
|
Always use pointers for structs (efficiency and API consistency)
|
Struct Parameter Passing
| |
Type Selection and Usage
|
|
| |
AVMCCS-T001
|
Use size_t for sizes, lengths, and array indices
|
Size and Index Types
| |
AVMCCS-T002
|
Use proper types for pointer operations (uintptr_t, ptrdiff_t)
|
Pointer Arithmetic
| |
AVMCCS-T003
|
Use explicit-width types from <stdint.h> when size matters
|
Fixed-Width Types
| |
AVMCCS-T004
|
Choose appropriate pointer type based on semantic intent
|
Pointer Type Usage
| |
AVMCCS-T005
|
Always use const for read-only parameters
|
Const Correctness
| |
AVMCCS-T006
|
Use const for arrays of strings to ensure they’re stored in read-only memory
|
Const Correctness
| |
AVMCCS-T007
|
Place const before the type for readability
|
Const Correctness
| |
AVMCCS-T008
|
Use typedef for types exposed in headers
|
Typedef Usage
| |
AVMCCS-T009
|
Always use typedef for function pointers
|
Typedef Usage
| |
AVMCCS-T010
|
Use opaque types in public headers to hide implementation
|
Forward Declarations
| |
AVMCCS-T011
|
Forward declare to minimize header dependencies
|
Forward Declarations
| |
AVMCCS-T012
|
Use forward declarations to break circular dependencies
|
Forward Declarations
| |
Memory
|
|
| |
AVMCCS-M001
|
Order structure members from largest to smallest
|
Structure Alignment
| |
AVMCCS-M002
|
Use bit fields sparingly with specific rules
|
Bit Field Conventions
| |
AVMCCS-M003
|
Convert network/file data to host byte order
|
External Data Considerations
| |
AVMCCS-M004
|
Use memcpy for potentially unaligned external data
|
External Data Considerations
| |
AVMCCS-M005
|
Always check allocation failures
|
Heap Allocation
| |
AVMCCS-M006
|
Every allocation must have a corresponding deallocation
|
Heap Allocation
| |
AVMCCS-M007
|
Limit stack arrays to ~256 bytes
|
Stack Allocation Limits
| |
AVMCCS-M008
|
Only use big stack arrays in leaf functions
|
Stack Allocation Limits
| |
AVMCCS-M009
|
Never use VLAs or alloca() - they prevent static stack analysis
|
Stack Allocation Limits
| |
AVMCCS-M010
|
Prefer fixed-size arrays over VLAs - predictable is better than minimal
|
Stack Allocation Limits
| |
AVMCCS-M011
|
Use _init for simple initialization
|
Simple Initialization
| |
AVMCCS-M012
|
Use _create/_destroy for stack-friendly resource management
|
Stack-Friendly Resource Management
| |
AVMCCS-M013
|
Use _new/_delete for heap-allocated objects
|
Heap-Allocated Objects
| |
Documentation and Comments
|
|
| |
AVMCCS-D001
|
All public APIs require minimum brief description
|
What to Document
| |
AVMCCS-D002
|
Document parameter relationships
|
What to Document
| |
AVMCCS-D003
|
Document memory ownership and lifetime requirements
|
What to Document
| |
AVMCCS-D004
|
Document units of measurement even when in parameter names
|
What to Document
| |
AVMCCS-D005
|
Document preconditions and postconditions
|
What to Document
| |
AVMCCS-D006
|
Document side effects and thread safety
|
What to Document
| |
AVMCCS-D007
|
Document error handling behavior
|
What to Document
| |
AVMCCS-D008
|
Document non-obvious algorithmic complexity
|
What to Document
| |
AVMCCS-D009
|
Use formal documentation system for public APIs
|
Public vs Private APIs
| |
AVMCCS-D010
|
Use Javadoc-style comments for Doxygen
|
Public vs Private APIs
| |
AVMCCS-D011
|
Complete parameter documentation with directions
|
Public vs Private APIs
| |
AVMCCS-D012
|
Include examples for non-trivial usage
|
Public vs Private APIs
| |
AVMCCS-D013
|
Document all error conditions
|
Public vs Private APIs
| |
AVMCCS-D014
|
Standard level: Add parameters, return values, and specific error codes
|
Documentation Levels
| |
AVMCCS-D015
|
Safety-critical level: Add preconditions, warnings, and detailed behavior
|
Documentation Levels
| |
AVMCCS-D016
|
Only annotate output and bidirectional parameters
|
Key Documentation Patterns
| |
AVMCCS-D017
|
Always document array/length parameter relationships
|
Key Documentation Patterns
| |
AVMCCS-D018
|
Clearly explain dual-purpose parameters
|
Key Documentation Patterns
| |
AVMCCS-D019
|
Be explicit about memory allocation and lifetime
|
Key Documentation Patterns
| |
AVMCCS-D020
|
Document units even when parameter names include units
|
Key Documentation Patterns
| |
AVMCCS-D021
|
All public macros require documentation
|
Key Documentation Patterns
| |
AVMCCS-D022
|
Default to comments above code blocks
|
Comment Style
| |
AVMCCS-D023
|
Use end-of-line comments sparingly, only for emphasis
|
Comment Style
| |
AVMCCS-D024
|
Use standardized tags (TODO, FIXME, HACK, WORKAROUND)
|
Comment Tags
| |
AVMCCS-D025
|
Never commit commented-out code without explanation
|
What to Avoid
| |
AVMCCS-D026
|
Avoid decorative comment blocks
|
What to Avoid
| |
Structure
|
|
| |
AVMCCS-S001
|
Document style exceptions at file beginning
|
Style Exception Documentation
| |
AVMCCS-S002
|
Headers follow specific organization pattern
|
Header Files (.h)
| |
AVMCCS-S003
|
Source files follow specific organization pattern
|
Source Files (.c)
| |
AVMCCS-S004
|
Use traditional include guards for maximum compatibility
|
Include Guards
| |
AVMCCS-S005
|
Group includes by origin with alphabetical ordering
|
Include Ordering
| |
AVMCCS-S006
|
Header files match module names using underscores
|
Header File Naming
|
Document Version: 1.0 Style Guide Name: AtomVM C Coding Style Guide (AVMCCS Guide) Last Updated: July 2025