[logo]ewhac

ewhac's C Coding Style

2019.05.21

Last updated: 2022.04.30

Every place I've worked has an established style standard for writing C code. And every time I read it, it becomes blindingly apparent that the local standard was informed not by a desire to make code more readable or easier for newcomers to understand, but rather by the personal style quirks of whoever happened to check in the first few files.

As of this writing, the last place I worked tried to establish a C style guide, which itself took cues from the MISRA C style guide -- which you have to pay money for! The resulting style guide was never formally ratified and never enforced. And that incident pretty much served as the impetus to finally write down my thoughts on the subject, accumulated by working with the language over the last 40 years, and which no one ever asked for, but for which you all shall now be judged.

These guidelines are also informed by what some may regard as a "primitive" method of writing code -- namely, with a stock install of Vim, a plain text editor with no code analysis or navigation assists.

I generally don't realize these individual points until I encounter it done wrong somewhere. So this document will accrete over time.

It took me several years to finally realize this, but the Guiding Principle behind the all the ranting and armwaving up ahead is this:

Don't make me hunt for important things.


File Format

C source files should be written with "UNIX" style newlines (single linefeed character (0x0A, ^J, \n) ends each line). If you are editing on a platform that defaults to something different, reconfigure your editor.

All lines in the source file, including the last line, shall be terminated with a newline.

Hard TAB characters (0x09, ^I, \t) are modulo eight spaces. Period.

File content shall be 7-bit ASCII, or UTF-8 with no Byte Order Marker (BOM).

Code Formatting

Note that all the below rules are in fact guidelines. If, at any location in the code, the enforcement of a given guideline would detract from the Guiding Principle, then the rule may be modified or ignored. The goal is readability, not slavish compliance to a ruleset. (Corollary: It is impossible to write an automated tool to enforce these rules, since readability at any given point in the code is subjective based on the surrounding code.)

Indentation

TABs or Spaces: Pick ONE for all your code. Do not intermix TABs and spaces for code indentation (except as noted below).

Space-based Indentation

If you choose spaces to indent your code, TAB characters should appear nowhere in your file except as part of string literals (and even there you should be using "\t").

TAB-based Indentation

If you choose to use TABs to indent your code, understand first that you're signing up for eight spaces per indentation level. If that's too big for you, use spaces.

Though it requires extra effort to do so, you should use TABs only to establish base indentation level. Lining up code fragments on a given indentation level should be done with spaces.

Typeface Selection

Use fixed-width, non-proportional typefaces in your editor when writing code.

Variable Declarations

All variable declarations in a block shall appear at the beginning of said block.

Separate variable type and name into columns. This applies to declarations of local and global variables, and to fields in a struct.

When declaring types, the pointer decorator goes next to the variable name, and not next to the type.

Declaration Order – Variables and Structure Fields

Declare variables and structure fields in order from largest to smallest object.

Where possible, try to group together variables and fields that have a relationship to each other. This is admittedly vague and very subjective.

Declaration Order – Function Parameters

If the function operates on a struct or other data in an object-like fashion, the pointer to that data -- the thing upon which the function operates -- appears first. "Output" parameters, if any, appear next, followed by "input" parameters.

If a passed pointer requires a companion size (e.g. maximum length of a buffer), the size parameter immediately follows the pointer.

Brace Position

Wikipedia actually has a surprisingly nice discussion on various schools of thought regarding brace positioning, including their historical origins. My preferred embodiment most closely resembles "K&R" style.

In general, braces live at the same indentation level as the parent block or control statement.

With limited exceptions, nothing appears to the right of an opening brace.

If the block structure remains obvious when the brace is on the same line as the control statement, then keep the brace on the same line (exception: function declarations). Otherwise, place the brace on its own line.

Switch-Case Layout

case statements are aligned with their parent switch statement.

Try to leave a blank line between the case statement and the code above it, if any.

Unless there is a technical reason to do otherwise, place the default case at the bottom.

Using fallthrough is fine; just be clear about it. Old versions of the lint syntax checker would warn against fallthrough, but the warning could be squelched by adding the comment /* FALLTHROUGH */ at the end of the case, as if to say to lint and the reader, "I meant to do that." The Linux kernel sources also introduces the synthetic keyword fallthrough for precisely this purpose.

Duff's Device: Be very clear about what you're doing. But also be sure you actually need it -- benchmarks show Duff's Device as often slower than the optimizations and loop-unrolling provided by modern compilers. (Yeah, surprised me, too.)

Spacing

Operators

Unary arithmetic operators (-, ~) shall have no space on their right-hand side.

Binary arithmetic, bit-wise, and comparison operators (+, -, *, /, |, &, ^, ==, !=, etc.) shall be surrounded on each side by at least a single space.

Binary logical-and and logical-or operators (&&, ||) shall by surrounded on each side by at least two spaces.

For-Loops

If all three clauses of a for-loop will fit on a single line, separate each clause by two spaces following the semicolon. Where the clauses of the for-loop need to subtend multiple lines, place each clause on its own line.

Structure Field Assignment

When assigning values to fields of a given struct, arrange the fields and values into columns. The = operators should be aligned on the right-hand column.

Function Declarations, Calls, and Arguments

For function declarations and calls, a space shall appear between the function name and the opening parenthesis (exception: Function calls with no arguments). No space shall follow the opening parenthesis.

At least once space shall follow each comma in the parameters to a function call.

For function declarations, the function's return type, including the pointer decorator (if any), appears on its own line; the function's name shall begin in the first column of the following line. This guideline does not apply to forward declarations or prototypes.

Line Wrapping

When wrapping long lines, break the lines before operators such that the operators are aligned on the left. Align under the = operator or the enclosing opening parenthesis where practical.

Improved Idiomatic Habits

When taking the size of a structure or type, take the sizeof() the variable that holds the allocation, not the sizeof() the type.

C Language Misfeatures

Bitfields

Avoid using C's bitfield feature in general code. Never use the bitfield feature in code that accesses hardware registers.