C style guidelines (mario style)
This is supposed to be a rather general but opinionated C style guideline set for mainly myself. The main design constrains are:
- consistency
- readibility
- accesibility
- simplicity
- idiomacy
These guidelines are meant for C99 and C11.
Download the markdown document here, you should copy it to your project as a STYLE.md file on the root of your project so contributors can see it, and maybe link to it on the CONTRIBUTING.md file, if you’ve got one. Or link to this page.
(These guidelines are under the public domain, see this file for more information)
1. C is not C++
It’s common to see function pointers used to imitate methods, like a C++ class.
The problem is that C is not C++, C is a procedural language, and C++ is an OO one. Only use function pointers to store functions when the functions are supposed to be data, e.g., callbacks, dispatch tables, …
Bad
struct foo
{
void (*init)(void);
int (*do_something)(struct foo* self);
bool (*do_something_else)(struct foo* self);
void (*end)(void);
};
Good
struct event_callback_mapping
{
Event* event;
void (*callback)(Event* event);
};
On the first example, we use function pointers as methods, on the second we use it for mapping an event to a callback.
2. Format your code well
Formatting is important; if code is badly formatted other people (and you in the near future) will have a hard time digesting code, so apply these rules consistently:
2.1. Indentation
- Always use TABs
- Set tabs to be equivalent to 8 spaces
The rather large indenation size is mostly because of readibility, it also makes it easy to see when you’re nesting too much.
2.1.1. Switch statements
Don’t indentate cases on a switch statement.
Bad
int foobar(int foo, int bar)
{
int x = foo + bar;
switch (x) {
case x:
...
case y:
...
}
}
Good
int foobar(int foo, int bar)
{
int x = foo + bar;
switch (x) {
case x:
...
case y:
...
}
}
2.3. Types
There must always be a newline character between a function’s type and its name on definitions:
int
foo(void)
...
For prototypes, put type and name on the same line, to save space mostly:
int foo(void);
2.4. Braces
For top-level construct, put braces on separate lines, the rest attach the braces to the same line:
enum flags
{
FLAG_ACK = (1 << 0),
FLAG_END = (1 << 1),
FLAG_PRIORITY = (1 << 2),
};
bool
foo(uint64_t flags)
{
if (flags & FLAG_PRIORITY) {
puts("very important message");
return true;
}
return false;
}
While you can omit braces on if statements, don’t mix braces and no braces:
Bad
if (strcmp(a, b) != 0) {
something();
return false;
} else if (!a)
return false;
else if (!b) {
puts("b is missing");
return false;
} else
return true;
Good
if (strcmp(a, b) != 0) {
something();
return false;
} else if (!a) {
return false;
} else if (!b) {
puts("b is missing");
return false;
} else {
return true;
}
It’s annoying and awkward to read. Don’t.
2.5. Pointers
Align pointers to the left, as something is a pointer type not a pointer variable.
Good
void* foo;
char* bar;
Bad
void *foobar;
char *baz;
For declaring multiple pointers with a comma, append the star, then the space, and then the variable name:
int* x,* y,* z;
3. Comments
Don’t use C++-style //, only C-style /* */, and don’t comment what’s obvious:
Bad
/* Increase i */
i++;
Instead, comment what isn’t immediately obvious:
Good
/*
* The protocol usually uses host endianness, but for checksums, Network Byte Order must be used.
*/
data = to_nbo((uint64_t)checksum);
For example, comment:
- invariants
- protocol nitpicks
- weird tricks
- …
For important comments, wrap them like this:
/*
* comment
*/
3.1. Sections
For organization, files should also be divided into logical sections: normally:
- constants
- variables
- function prototypes
- helpers
- important functions
- main, sometimes
(Some people also like to add an includes section too.) Use /*** ***/ for this:
/*** function prototypes ***/
void foo(int x);
void bar(int y);
### 3.2. Documentation
For documenting something, start with two asterisks (`/** */`), and add a newline in between if the documentation isn't a simple single line:
> **Good**
/** * Do some work that is very important. * * Balgh, blagh… */ void important_work(void); ```
3.3. Start of files
To start a file:
/*
* file -- short description
*
* longer, optional description
*/
If you want, add an SPDX license identifier line:
/* SPDX-License-Identifier: CC0-1.0 */
/*
* file.c -- does some useful thing
*/
4. Portability
Use C99 or C11 specifically, and use standard C facilities.
GNUisms are evil. Try to not use GNU-specific extensions and keep your code standards-compatible, although you can use non-ISO __LINE__ and __FILE__; since virtually every C implementation supports these. Avoid:
5. Compile-Time Asserts
Try to use _Static_assert aggressively, as there are a lots of things your program may require at compile time that can or can’t be:
_Static_assert(sizeof(Header) == 32
"Header must be 32 bits");
6. Naming
Names shouldn’t be verbose; nor cryptic. Try to avoid names void of value:
Bad
int a;
int b;
int tmp2;
Good
int status_http;
int fd_conf;
size_t response_length;
That being said, there are many abbrebiations that are widely understood:
- buf -> buffer
- len -> length
- argc -> argument count
- argv -> argument vector
- fd -> file descriptor
- pid -> proccess id
- …
6.1. Naming Conventions
- Macros:
SCREAMING_SNAKE_CASE - Variables:
snake_case - Functions:
snake_case - Typedefs:
PascalCase
6.2. Short names
While it’s always a good idea to have descriptive and readable names, you can get cryptic with loops:
for (size_t i = 0; i < count; i++)
...
Mathemathical code also often uses single-letter variables.
Don’t be afraid of introducing Unicode if it does improve readability, but C isn’t APL.
6.3. Application Hungarian Notation
Hungarian Notation is the practice of prefixing variable names with the type. The classic type of hungarian notation, systems hungarian notation, is superfluous, but there’s also application hungarian notation, which annotates stuff that the type can’t provide. For example:
Good
int fd_conf = fileno(config_file);
int status_http;
Bad
int i_count;
char* sz_name;
6.4. Naming functions
Functions should be named after actions, in infiitive normally:
Good
buffer_read()buffer_write()config_parse()
Name related functions consistently:
Good
buffer_read()buffer_write()buffer_close()
Bad
read_buffer()destroy_buf()buffer_close()
Prefixes are very useful for making it clear what a function operates on, but don’t prefix functions merely to encode types:
Bad
int_buffer_read()
6.5. Naming libraries
Add prefixes to all the declarations of your library, but don’t make users type an awfully long name:
Bad
libeasyaudiodecodersystem_init();
Good
audiodec_init();
You can abbreviate names a bit.
6.6. Boolean values
Booleans should be read as a question or statement:
Good
bool is_validbool has_headerbool enabled
Avoid names that read like they hold a value instaed of truthfulness:
Bad
bool flagsbool checks
7. Variable declarations
Try to declare variables near the beggining of a block (a block is considered any scope), to make it esier to scan visually.
This isn’t a mandatory requirement, and many times not declaring something on the beggining is better, but try:
Response
parse_response(char* buf)
{
Header headers[];
int status_http;
...
for (size_t i = 0; i < sizeof(headers) / sizeof(headers[0]); i++) {
char *name;
char *value;
...
}
}
8. Function size and complexity
A function’s maximum length should be inversely proportional to its complexity. A simple function (such as an extension-to-MIME type conversor) can have many lines and low complexity, while more complex functions should be split into multiple to avoid it becoming too complex.
An useful rule of thumb is: “can a reader that isn’t me understand what this function’s doing without simulating a program on his mind?”
9. Arrays
For getting the number of elements on an array, you can use:
sizeof(array) / sizeof(array[0])
e.g.:
for (size_t i = 0; i < sizeof(headers) / sizeof(headers[0]) {
...
}
If you find yourself doing this too often; you can introduce and array-count macro.
9.1. Variable-Length Arrays
VLAs are allowed, but only use them when they make code clearer and size and lifetime are straightforward, since they can become a mess fast.
10. Memory allocation
Use the object size idiom.
Good
foo_t* x = malloc(sizeof(*x));
Bad
foo_t* x = malloc(sizeof(foo_t));
Instead of repeating the type, as if the type of x changes you don’t have to change the malloc too.
Multiply for allocating arrays:
int numbers[number_count];
(int*)numbers = malloc(number_count * sizeof(*numbers));
Don’t cast malloc calls unless necessary.
11. Line Length
The maximum line length is 80 columns.
A tab counts as 8 characters.
12. Multiline function calls
When a function call should be split into lines to meet the line length limit, format it like this:
function_with_many_args
( arg1,
arg2,
arg3 );
12. goto
Contrary to popular belief, goto doesn’t summon a dinosaur.
goto is actually really useful in C, since C doesn’t feature defer, exceptions, RAII, …, it can make cleanup much simpler.
While goto is useful for error handling and a couple of other cases, don’t use it for control flow. This is why goto is much critisized; because it’s really easy to abuse. Prefer if, for, while, switch and functions over goto if it expresses the logic better; which is like 90% of the time.
Good
int
do_something(void)
{
void* data;
FILE* file;
int status;
data = malloc(1024);
if (!data)
return -1;
file = fopen("file", "r");
if (!file) {
status = -1;
goto cleanup;
}
...
status = 0;
cleanup_data:
fclose(file);
free(data);
return status;
}
12.1. Error Handling
Keep in mind you need to unwind resources in reverse order. Define multiple labels:
int
foo(void)
{
Resource* first;
Resource* second;
int status;
first = init_first();
if (!first)
return -12;
second = init_second();
if (!second)
goto free_first;
status = do_work(first, second);
if (status != 0)
goto free_first;
free_second:
release_second(second);
free_first:
release_first(first);
return 0;
Keep labels descriptive; names like free_buffer or cleanup are much better than foo_sub3.
12.2. Don’t abuse it
This is where goto’s criticism comes from: its ease to be abused.
Bad
int
bad_code(void)
{
start:
if (condition) {
...
goto start;
} else {
goto end;
}
end:
...
}
Good
int
good(void)
{
while (condition) {
...
}
...
}
Try to only do forward jumps, as they are more straight-forward (;)) than backwards jump, and don’t jump to the middle of scopes.
13. White space
Keep your files breathable by inserting newlines and whitespace whenever you think the code’s getting dense. When this happens is personal preference; but usually after blocks, after a sequence of operations on a variable or value, …
14. Error handling
After using a function that may set errno, call perror() if it failed! Don’t try to make your own strerror or whatever as the default diagnostics should be good enough by default.
For functions that may fail should:
- return a
boolvalue if it just either succeeds or fails, wheretruemeans success andfalsemeans failure - return a
ssize_tvalue if it is supposed to return a size_t, when positive it encodes the success value and when negative an error - return
NULLon failure if the function is supposed to return any pointer. - return an error enumeration or tagged union for more context
14.1. Error enumerations
To add context to errors, an enum holding the different types of errors is a good idea. Prefix the error with ERR, and if this is a library the library name of course:
enum errors
{
ERR_OK,
ERR_MEMORY,
ERR_NO_INTERNET,
ERR_BAD_RESPONSE,
ERR_BAD_ARGUMENTS
};
Always add an ERR_OK variant.
15. Public API
It’s specially important to keep your public headers clean, as users will naturally seek function references in them. Make it easy.
15.1. Typedefs
Only use typedef when it’s actually necessary. On public APIs for example, it’s almost always. Keep the regular struct-prefixed type accessible though, and put the typedefs on the beggining of the API defs:
Bad
typedef struct {
...
} MyType;
Good
typedef struct my_type MyType;
...
struct my_type
{
...
};
15.2. Arguments
On prototypes, always write down the names, and try to not abbreviate:
Good
int clamp(int minimum, int maximum, int value);
Bad
int clam(int, int, int);
In the first, the user knows what each parameter does, on the latter they don’t.
15.3. Ownership
Ownership in C is a pain in the ass to do, so make it easy for your users to know by writing it down instead of making them figure them out on their own.
Use a comment for this, e.g.:
/**
* Make a Response structure from a raw response buffer.
*
* Structures returned by this function are allocated in
* memory and owned by this function, the caller must free the result.
*/
Response* parse_response(char* buffer);
15.4. Opaque types, static & baisc API hygiene
Keep internal types—and the actual data in a structure—away from the user. This makes it harder for the user to mess up by using some internal function instead of what the public API provides.
For single-header libraries (stb style), hide internal functions with __. Those are two underscores.
Provide getters and setters instead:
Good
/**
* api.h -- api
*/
...
int get_timeout_ms(Connection* connection);
void set_timeout(Connection* connection, int timeout_ms);
Request get_current_request(Connection* connection);
---------------------------------------------------------
/**
* file.c
*/
...
if (waiting_for >= get_timeout_ms(conn))
stop_processing(get_current_request(conn));
Bad
if (waiting_for >= conn->timeout)
stop_processing(conn->current_req);
This gives you flexibility and lets you change fields without breaking all users of the library, which all users of your library will love.
To post a comment you need to login first.