Code style for C and C++
One set of naming and lifecycle rules for both languages. C++ adds explicit ownership and a small set of own types in public interfaces.
Rules for both languages
- snake_case for functions and variables, Pascal_case for types, ALL_CAPS for constants. File-private state is static (C) or private (C++); the keyword says it, the name does not need to, so a private field has no leading underscore. Reason: the C standard library is spelled in lowercase, so this is the closest fit to the canonical form.
- In a definition, a space before the parenthesis; in a call, none. Reason: definitions become easy to find by text search (a local convention, see the overview for its limits).
- Every resource has the lifecycle init, use, free, with the pair visible to the caller. No hidden allocation.
- Expected failures are return codes or a small status type. Abort paths are for programmer errors, not for validation.
- Document which pointers may be null. Use assert for invariants that hold after a successful init.
- One primary concept per module; no misc.c or misc.cc.
- Named constants instead of magic numbers.
- Braces: Allman, with a narrow exception for a single-line guard such as an early return.
Additional rules for C++
- Headers are .h, implementation files are .cc.
- Public interfaces show the project's own types, not standard library types. Reason: a library type in a header becomes part of the contract.
- Pointers carry meaning by convention: Type* may be null and the caller checks, Type& must exist, a slice is borrowed for the caller's lifetime.
- Expected failures return a Result. Exceptions are not used for ordinary validation.
- Heap allocation lives inside a dedicated data-structure module. Everything else defaults to the stack.
- Rules that exist only for C++ (templates, slices) are not applied to plain C.
Generator mapping
Both languages keep the canonical word order and separators. A project prefix keeps the flat C namespace tidy.
| Canonical | C | C++ |
|---|---|---|
load_url (function) | load_url | load_url |
web_view_configuration (type) | Web_view_configuration | Web_view_configuration |
minimal_ui (constant) | MINIMAL_UI | MINIMAL_UI |
A binding that targets C++ users of an existing library may choose that library's spelling instead; the canonical name stays the same.
Example in C
typedef struct Reader Reader;
Reader* reader_open (const char* path);
int reader_read (Reader* reader, char* buffer, size_t capacity);
void reader_close (Reader* reader);
int copy_first_block (const char* path, char* buffer, size_t capacity)
{
Reader* reader = reader_open(path);
if (reader == NULL) return -1;
int count = reader_read(reader, buffer, capacity);
reader_close(reader);
return count;
}
Example in C++
Result open_page (Page_view& view, const Url& url)
{
if (!url.is_valid())
{
return Result::error(ERR_INVALID_URL);
}
return view.load_url(url);
}
Counter-arguments
- Widely used C styles, such as the Linux kernel's, use tabs and K&R braces. Own-line braces are uncommon in C.
- Many C libraries prefix names instead of using a Pascal_case type name, which keeps everything lowercase.
- A text search for name ( finds definitions only if every declaration, function pointer and macro follows the same rule. They often do not.
- The C++ Core Guidelines recommend managing resources automatically with RAII and using unique_ptr or shared_ptr to represent ownership. This convention deliberately goes the other way for C++: explicit, visible acquire and release calls, and no standard smart pointers in this ownership model. The price is more manual code and the loss of what many C++ programmers and tools expect.
- Keeping standard library types out of public headers costs conversions and duplicates well-tested code.