My C/C++ style guide
May 26th, 2026
Throughout my years of programming, I've grown to prefer the simpler things. The most glaring example of this is
how
I approach software architecture. Beyond the specific rules listed below, I've fully embraced the Handmade ideology. My primary inspiration for code
style is Epic Games' raddebugger, though
I
diverge in a few key aspects to
better suit my workflow.
Program Structure:
My more complex programs are structured as a series of layers - each layer acting as its own distinct section of
the
program. Physically, these are represented as individual directories at the top level of the project. A strict
rule
is that layers must not form cyclical dependencies between one another, nor should they nest inside other
layers.
Every layer must be usable on its own (alongside its required dependencies) in a meaningful way.
Logically, layers also serve as namespaces. Every object within a layer (functions, structs, etc.) uses a short
prefix inspired by the layer's name, followed by an underscore (e.g., an object in the
optimize
layer
gets the
prefix opt_).
Depending on a layer's complexity, I also like to include a
README.md in its top-level directory to
provide
necessary context for anyone navigating the codebase.
Headers and source files:
Unless there are obvious performance implications, I tend to shy away from the
inline keyword. I
prefer the
traditional approach of splitting declarations and definitions between header and source files.
I also stick to the "old-school" method of using include guards:
...
This is purely because I still interact with older or specialized compilers that don't reliably support
#pragma
once. Additionally, I abstain from using multi-line comments (/* ... */), exclusively opting
for single-line
lowercase comments.
Naming scheme:
Here is a quick primer on how I name variables, functions, and types:
- Types: All types use
CamelCase(e.g.,SharedBatch). Enums are suffixed withEnum(e.g.,SectionTypeEnum). - Enum members: Every member of an enum is prefixed with the enum's name (minus the suffix) and an
underscore.
For
example, a member of the aforementioned enum would be named
SectionType_StringTable. - Functions & variables: All function identifiers and non-constant variables use
snake_case. - Constants: Constant variables use
CamelCase. - Special variables: Global or device variables use a single-letter prefix, such as
g_contextfor a global context. If multiple qualifiers apply, they are combined - for example,gd_contextfor a global device context. - Macros: With the exception of include guards, macros use
CamelCase.
Core types:
I prefer shorter names for primitive types. I consistently use these typedefs across my projects:
typedef uint8_t U8;
typedef uint16_t U16;
typedef uint32_t U32;
typedef uint64_t U64;
typedef int8_t I8;
typedef int16_t I16;
typedef int32_t I32;
typedef int64_t I64;
typedef float F32;
typedef double F64;
typedef int32_t B32; // 32-bit boolean
typedef char8_t C8;
I also expose a few core types that I use everywhere. The most essential is a basic string:
typedef struct Str {
C8* ptr; // null-terminated at ptr[size]
U64 size;
} Str;
The extra cost of those 8 bytes for the size is negligible in most applications, and it easily outweighs the
overhead of repeatedly calculating string lengths at runtime. I'm currently toying with a 4-byte size variant
for
applications where a full 64-bit size isn't required (perhaps naming the larger one
LongStr). I'm
also considering
renaming my basic string type to S8, though I haven't fully committed to the switch yet. Since I
rarely work with
UTF-16, I don't currently maintain a wider string type.
Memory management:
Lately, I've adopted Ryan Fleury's Arena approach for managing lifetimes. Through this, I've realized I
essentially
have no need for dynamic arrays or vectors. A simple implementation like the one below has been more than enough
for
all of my projects so far:
typedef struct ArenaBlock {
struct ArenaBlock* next;
U64 size; // total bytes in base
U64 used; // bytes currently used
U8 base[]; // C99 flexible array member
} ArenaBlock;
typedef struct Arena {
ArenaBlock* head;
U64 block_size;
} Arena;
Macros:
Beyond the naming scheme mentioned earlier, I prefer using a comment to explicitly highlight what an
#endif block
is
closing. My most heavily used macro is probably my custom Assert:
Along with some standard mathematical must-haves:
Error handling:
From my experience, a highly robust approach to error handling is defining a set of non-zero error codes, with
0
representing success. With this scheme, there is no issue passing an I32 value down the stack and
eventually (in
the
case of a fatal error) propagating it directly into the entry point's return value to be handed off to the OS.
This
pattern is especially useful as a global standard for constructors and other operations that can encounter a
failure
state.
Misc:
Finally, always
typedef all structures (typedef struct MyStruct { ... } MyStruct;) and
enums. Additionally, enums
should typedef the underlying integer type meant for storing its members. For example, for
SectionTypeEnum, this
would look like: typedef U32 SectionType;. I also limit myself to a 100 character line width.