Skip to content

hlasitahudbabezzvuku/Conventions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HlasitaHudbaBezZvuku's C Conventions

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.

Naming Conventions

Struct Definitions

  • Use PascalCase
struct Node
{
    // ...
};

Union Definitions

  • Use PascalCase
  • U prefix (U for Union)
  • Do not use typedef
union UType
{
    // ...
};

Enum Definitions

  • Use PascalCase
  • E prefix (E for Enum)
  • Do not use typedef
enum ELogLevel
{
    // ...
};

Enum Values

  • Use MACRO_CASE
  • E_<enum_name>_ prefix (capitalized with underscores)
enum ELogLevel
{
    E_LOG_LEVEL_ERROR,
    E_LOG_LEVEL_WARNING,
    E_LOG_LEVEL_INFO,
};

Preprocessor Macros

  • 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]; })

Global Variables

  • Use snake_case
  • g_ prefix (g for Global)
  • Use sparingly; restrict access via functions if possible
extern int32_t g_lexer_worker_count;

Local/Stack Variables

  • Use snake_case
  • Includes function parameters that are not pointers
uint32_t result_code;

Pointer Variables

  • Use snake_case
  • p_ prefix (p for Pointer)
  • Applied to all pointer types, including function parameters.
uint32_t *p_buffer;

Public Functions

  • 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);

Static Functions

  • Use snake_case
  • s_ prefix (s for Static)
  • Use for internal functions
static int32_t s_validate_id(const int32_t id);

Private/Helper Functions

  • 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 69

Project Structure Conventions

The project should use a structured, modular organization to isolate functionality:

  1. The root directory hosts configuration and build files (e.g., Makefile, .clang-format, .gitignore, etc.)
  2. The Source/ directory should contain all C source code in form of modules (sub-directories)
  3. Each functional module (e.g., List, Networking, Logger) should reside in a dedicated directory named in PascalCase inside Source/
  4. Files within a module directory should named in snake_case (e.g., list.h, list.c)
  5. All publicly accessible functions defined in a module should be prefixed with the module's name in snake_case (e.g., functions in Source/List would be prefixed list_...)
╴ <project>/
    ├╴ .clang-format
    ├╴ .clangd
    ├╴ .gitignore
    ├╴ Makefile
    ├╴ Build/
    │   └╴ ...
    └╴ Source/
        ├╴ main.c
        ├╴ Example/
        │   ├╴ example.h
        │   └╴ example.c
        └╴ ...

Coding Style and Object-Oriented C

C Standard and Compiler

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.

Type Safety and Immutability

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:

  1. Instead of int or long use types from <stdint.h> (e.g., uint32_t, int16_t)
  2. Use uint_t for counts, loop iterations, and array sizes
  3. 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:

  1. Any pointer passed to a function that should not be modified should be declared with const
  2. All program constants that are not macros should be declared with const.

Formatting and Readability

The codebase should enforces code formatting using the .clang-format file at the repository root.

Braces

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)
    {
        // ...
    }
}

Pointers

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);

Struct Definitions

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
{
    // ...
};

Header Files and Includes

Header Guards

All header files (.h) should use the standard preprocessor guards:

#ifndef MODULE_FILE_H
#define MODULE_FILE_H

// ...

#endif

Include Ordering

Includes should be separated by a single blank line:

  1. System/Library includes using angle brackets (<...>)
  2. Local/Project includes using double quotes ("...")
#include <stdio.h>
#include <stdbool.h>

#include "module_private.h"
#include "../Common/common.h"

Function Parameters

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-explanatory

Object-Oriented Function Conventions

Use an object-oriented patterns focusing on clear memory and initialization management:

  1. For structs allocated on the stack or by the caller, use an _init function
  2. For objects managed on the heap, use a pair of _new and _free functions
  3. The _free function is mandatory for any struct that manages heap memory (even if it reside on stack)

Stack Allocation

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);

Heap Allocation

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);

Memory Deallocation

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks