home blog show lab fav res ideas tools

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:
#ifndef SOME_HEADER_H
#define SOME_HEADER_H
...
#endif // SOME_HEADER_H
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:

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:
#define DebugBreak() raise(SIGTRAP)
#define Assert(__condition, __message, ...)        \
do {                                               \
	if(!(__condition)) {                           \
		fprintf(stderr, __message, ##__VA_ARGS__); \
		fflush(stderr);                            \
		DebugBreak();                              \
	}                                              \
} while(0)
Along with some standard mathematical must-haves:
#define KB(n) (((U64)(n)) << 10)
#define MB(n) (((U64)(n)) << 20)
#define GB(n) (((U64)(n)) << 30)
#define TB(n) (((U64)(n)) << 40)

#define Min(a, b) (((a) < (b)) ? (a) : (b))
#define Max(a, b) (((a) > (b)) ? (a) : (b))

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.