Introduction
C is a programming language that can be styled in many ways. This has led to some humorous jokes by many people but everyone must choose a side in this debate.
That being said, here is my flame war material for this debate.
Basics
I have a few basic style preferences that I like to follow:
- Kernighan & Ritchie style brackets
- 2 space indents (Not tabs)
- 80 column limit for text
- Indent labels in case statement and indent the contents to be 1 indent deeper than the labels
- Pointers always to the right
I use an ungodly bash 1 liner to format everything I need.
bash -c 'find ./src -type f \\( -iname \\*.h -o -iname \\*.c \\) -exec clang-format -i -style="{IndentWidth: 2,TabWidth: 2,IndentGotoLabels: true,IndentCaseLabels: true,KeepEmptyLinesAtTheStartOfBlocks: true,ConstructorInitializerIndentWidth: 2, ContinuationIndentWidth: 2,}" {} \\;'
Which if you’re not interested in badly put together bash 1 liners a clang format file can be found here.
Brackets
I’m very fussy about how brackets look and where to use them. A comprehensive list of when to use brackets in my code looks like this:
- Avoid brackets when doing 1 liners
- Don’t wrap the contents of a switch case’s label in brackets
- Put an extra set of round brackets around a ternary statement’s condition
- Wrap negative casts in brackets (
(void *)(-ENOENT)
not(void *)-ENOENT
)
These are all just aesthetic and don’t really have a reason why I do them.
1 liners
This one upsets people a little especially people who program more in other C like languages like Java. But, I prefer to have all my one liners be without brackets like Kernighan & Ritchie did in the famous ansi C book. Difference is, I do it all the time. This leads to what some call ugly code:
while (1)
;
I prefer my code looking this way because I can then see the loop body clearly.
Coding style
I never compare to 0. Instead of doing something like
if (var == 0)
Or
if (var == NULL)
rather do
if (!var)
Similarly, for if it is not 0.
I also never leave a variable undefined. So rather code something like:
size_t i = 0;
for (; cond; i++)
;
than
size_t i;
for (i = 0; cond; i++)
;
Even for if statements where it is guaranteed to be assigned. Rather assign 0.
Attempt to not nest code. So something like:
vfs_node_t *node = vfs_open(cwd, path, &THIS_PROC->euio, 1, 0, 0);
if (!node)
return -ENOENT;
if (IS_ERR(node))
return (int)(uint64_t)node;
if (!S_ISLNK(node->stat.st_mode))
return -EINVAL;
if (!SYSARG2 || !SYSARG3)
return -EINVAL;
Is better than if it was all nested together.
Cases
I have 2 different cases for 2 different purposes. I use snake_case for all variable names and functions and SCREAMING_SNAKE_CASE for macros and constants. Sometimes if a function has a short enough and readable enough form, I will write it in unixcase.
Header file format
Header files always have a case guard of their basename plus an ‘H’, surrounded by double underscores in SCREAMING_SNAKE_CASE.
Function declarations always have their variable names in the declaration and if there are no arguments, do not add void.
Headers should be ordered in a certain way. After the opening guard, All constant declarations should follow, then macros, then structure definitions, then inline function definitions, then extern declarations, then function declarations as they appear in the C file this header matches to in question. All of these “sections” are separated by newlines except constants and macros.
So if we have a header file quax.h, it could contain:
#ifndef __QUAX_H__
#define __QUAX_H__
#define CNST 10
#define MACRO(x) ((x) * CNST)
typedef struct type {
int t;
int l;
double r;
} type_t;
static inline int x(int p) {
return p / CNST;
}
extern int quax_p;
int quax_foo();
void *quax_bar(char *baz);
#endif
Source file format
Very similar to header file format. Just that statics come after externs and global variables come after extern variables.
Static functions come before global ones where possible and the file is structured such that the functions rely on the ones on top of each other. So something like:
static inline int foo_l(int x) {
return x * 2;
}
int foo_p(int x) {
return foo_l(x + 2);
}
int foo_r(int x) {
return foo_p(x / 5);
}
Avoid function declarations in a source file like the plague. Every function should only depend on the ones above it
Declare static functions to also be inline. You might as well save a handful of instruction cycles.
Functions should be named as the file they’re from. So if the file is foo.c, then a file in it will be called foo\_bar()
. This allows me to have a sort of namespacing in C.
Project layout
A standard project of mine looks like this:
.
├── build
├── compile_flags.txt
├── config.mk
├── LICENSE
├── Makefile
└── src
├── foo
│ ├── include
│ │ └── foo.h
│ ├── Makefile
│ └── src
│ └── foo.c
└── bar
├── include
│ └── bar.h
├── Makefile
└── src
└── bar.c
So that 1 project (Say, an operating system) can have many separate programs and components that build together and interact. Due to every “project” having its own include directory, local includes are never used.
Conclusion
I repel people with my bad code styles.