Hi,
This repository hosts my personal set of conventions and templates for C and Bash development - an attempt to bring structured consistency to my own projects.
I first encountered C many years ago as a young student in the Czech Republic, learning the fundamentals from older books (around 1998… yes, I am young, you don’t have to tell me). While my path later led me through years of development in PHP with JavaScript (forced to do so by high-school), SQL, Java and finally .NET, the power and precision of C and the Unix shell always stayed in my hearth.
After a long hiatus, it is clear that while the core of the language remains, the environment has changed. Modern C development, especially when targeting *NIX systems with tools like GCC/Clang, requires standards and organizational structures that were not present in my older books.
This convention is a carefully considered blend: it respects the fundamental principles of C while integrating modern organizational patterns and the clear naming discipline often seen in object-oriented languages like C#.
The standard’s primary goal is to ensure consistency, enforce modularity. This includes strict rules for directory layout, function naming, object lifecycle management, and the use of the latest C23 standard features.
Note: This convention is strictly for my own development - a personal guide to ensure consistency and maintainability in my own code. It is not intended to replace or compete with established industry standards such as the Linux kernel style, Google C++ style, or others.
You are welcome to browse the detailed conventions and use any of the provided templates (e.g., Makefile, .clang-format, .clangd) as a structured starting point for your own projects.
- Use PascalCase
struct Node
{
// ...
};- Use PascalCase
Uprefix (Ufor Union)- Do not use
typedef
union UType
{
// ...
};- Use PascalCase
Eprefix (Efor Enum)- Do not use
typedef
enum ELogLevel
{
// ...
};- Use MACRO_CASE
E_<enum_name>_prefix (capitalized with underscores)
enum ELogLevel
{
E_LOG_LEVEL_ERROR,
E_LOG_LEVEL_WARNING,
E_LOG_LEVEL_INFO,
};- Use MACRO_CASE
<directory_name>_prefix (capitalized with underscores)- Separate functional macros by blank line
#define STACK_MAX_SIZE 69
#define STACK_POP(stack) ({ (stack)->data[--(stack)->size]; })- Use snake_case
g_prefix (gfor Global)- Use sparingly; restrict access via functions if possible
extern int32_t g_lexer_worker_count;- Use snake_case
- Includes function parameters that are not pointers
uint32_t result_code;- Use snake_case
p_prefix (pfor Pointer)- Applied to all pointer types, including function parameters.
uint32_t *p_buffer;- Use snake_case
<directory_name>_prefix (lowered with underscores)- Prefix should match the snake_case version of the parent directory name (e.g.,
Source/List->list_...) - Use void if function does not expect any intput;
List* list_new(void);
bool list_add(const List*, int32_t value);- Use snake_case
s_prefix (sfor Static)- Use for internal functions
static int32_t s_validate_id(const int32_t id);- Use snake_case
_prefix- Used for internal functions that are not static or macros
- Prefer
static(s_) unless sharing a private helper across multiple implementation files within a directory
int32_t _calculate_checksum(const char* p_string);
#define _MAX_TOKEN_LENGTH 69The project should use a structured, modular organization to isolate functionality:
- The root directory hosts configuration and build files (e.g., Makefile, .clang-format, .gitignore, etc.)
- The Source/ directory should contain all C source code in form of modules (sub-directories)
- Each functional module (e.g.,
List,Networking,Logger) should reside in a dedicated directory named in PascalCase insideSource/ - Files within a module directory should named in snake_case (e.g.,
list.h,list.c) - All publicly accessible functions defined in a module should be prefixed with the module's name in
snake_case(e.g., functions inSource/Listwould be prefixedlist_...)
╴ <project>/
├╴ .clang-format
├╴ .clangd
├╴ .gitignore
├╴ Makefile
├╴ Build/
│ └╴ ...
└╴ Source/
├╴ main.c
├╴ Example/
│ ├╴ example.h
│ └╴ example.c
└╴ ...The codebase should target the C23 standard, leveraging features like bool without requiring <stdbool.h>.
The code should be primarily optimized for GCC/Clang on *NIX systems and may use specific compiler extensions when beneficial and clearly documented.
The code should places a strong emphasis on type safety, explicit variable sizing, and immutability (const correctness).
To ensure cross-platform (architectures) consistency and clearly communicate the intended size of data, the following types should be used instead of standard types:
- Instead of int or long use types from <stdint.h> (e.g., uint32_t, int16_t)
- Use uint_t for counts, loop iterations, and array sizes
- For strings use standard char (not wchar_t) as it is same everywhere (UTF-8)
The const keyword should be used strictly to enforce immutability and prevent unintended side effects:
- Any pointer passed to a function that should not be modified should be declared with const
- All program constants that are not macros should be declared with const.
The codebase should enforces code formatting using the .clang-format file at the repository root.
All braces should be placed on a new line, separate from the preceding statement or function header (Allman style). This style is favored for its clarity in visually delimiting code blocks, similar to conventions in C#.
void example(void)
{
if (value > 0)
{
// ...
}
}The pointer asterisk should be aligned to the left, attached to the type name. This emphasizes that the asterisk is part of the type declaration (e.g., "this variable is of type 'pointer-to-int'").
int32_t* p_number;
void example(const char* p_string);The typedef should be on a separate line above the struct definition. This way it is clearly visible what the type is aliased to.
typedef struct Node Node;
struct Node
{
// ...
};All header files (.h) should use the standard preprocessor guards:
#ifndef MODULE_FILE_H
#define MODULE_FILE_H
// ...
#endifIncludes should be separated by a single blank line:
- System/Library includes using angle brackets (
<...>) - Local/Project includes using double quotes (
"...")
#include <stdio.h>
#include <stdbool.h>
#include "module_private.h"
#include "../Common/common.h"Parameter names should be omitted in function declarations unless the type is generic or the purpose of the parameter is non-obvious.
bool list_add(const List*, const int32_t value); // List* is self-explanatoryUse an object-oriented patterns focusing on clear memory and initialization management:
- For structs allocated on the stack or by the caller, use an
_initfunction - For objects managed on the heap, use a pair of
_newand_freefunctions - The
_freefunction is mandatory for any struct that manages heap memory (even if it reside on stack)
When using structure on stack, there should be a bool <name>_init(<struct>*) function that initializes the struct members when allocated on the stack.
// Heaader
bool example_init(const List* p_list);
// Unit
Example ex;
example_init(&ex);When using structure on heap, there should be a <struct>* <name>_new() functions that allocates memory on the heap, initializes the struct and returns the pointer to it.
// Header
Example* example_new(void);
void example_free(const Example **p_p_instance); // Takes ptr-to-ptr for safety
// Unit
Example *p_ex = my_struct_new();
my_struct_free(&p_ex);If struct lives on heap or uses heap in any other way, there should be a bool <name>_free(<struct>**) function that frees the heap memory. It must set the pointer to NULL to prevent double-free bugs. Requires a pointer-to-a-pointer (**).
// Header
void example_free(const Example *p_instance); // Stack
void example_free(const Example **p_p_instance); // Heap
// Unit
Example ex; // Stack
example_init(&ex); // Stack
example_free(&ex); // Stack
Example* p_ex = example_new(); // Heap
example_free(&p_ex); // Heap