This document describes C code style used by Andy Lee in his projects and libraries.
Table of Contents
- Andy Lee’s C coding style
General Rules
In short, consistency matters.
And, if you are working on upstream or existing projects, please follow the original coding style.
For example, Linux Kernel community requires its own coding style strictly, so we should follow Linux kernel coding style.
The link is the latest version currently, in case there is any updates, please always check the latest version.
Formatting
Mainly follow K&R style:
Indentation
Common Case
Use only spaces, and indent 4 spaces at a time.
We use spaces for indentation. Do not use tabs in your code. You should set your editor to emit spaces when you hit the tab key. For example, we can configure vim as follows.
1 | set tabstop=4 " set tab's width to 4 |
Exception Case
If you are developing Linux kernel, please
- Use tabs.
- All tabs are 8 characters.
- If your code is indenting too deeply, fix your code.
More detailed please visit Linux kernel coding style - Indentation.
Braces, Parentheses and Spaces Placement
Braces
- Opening brace last on the line.
- Closing brace first on the line.
- Exception for functions.
1 | // All non-function statement blocks (if, switch, for, while, do) |
Note that the closing brace is empty on a line of its own, except in the cases where it is followed by a continuation of the same statement, like a while in a do-statement or an else in an if-statement:
1 | do { |
and
1 | if (x == y) { |
Do not unnecessarily use braces where a single statement will do.
1 | if (condition) |
and
1 | if (condition) |
This does not apply if only one branch of a conditional statement is a single statement, it must keep the consistency.
1 | if (condition) { |
All while and do statements must have braces when a loop contains more than a single simple statement.
1 | while (ture) |
Parentheses
Parentheses () with key words and function policy.
- Do not put parentheses next to keywords, put a space between.
- Do put parentheses next to function name.
- Do not use parentheses in return statement when is not necessary.
- Do not add spaces around (inside) parenthesized expressions.
1 | if (condition) { |
Spaces
The use of spaces depends (mostly) on function-versus-keyword usage. Use a space after (most) keywords except sizeof, typeof, alignof and attribute, which look somewhat like functions.
So use a space after these keywords:
1 | if, switch, case, for, do, while |
but not with sizeof, typeof, alignof, or attribute. E.g.,
1 | s = sizeof(struct file); |
Use one space around (on each side of) most binary and ternary operators, such as any of these:
1 | = + - < > * / % | & ^ <= >= == != ? : |
but no space after unary operators:
1 | & * + - ~ ! sizeof typeof alignof __attribute__ defined |
no space before or after the increment & decrement unary operators:
1 | ++ -- |
and no space around the .
and ->
structure member operators.
Line Break
- The preferred limit on the length of a single line is 80 columns.
- Statements longer than 80 columns should be broken into sensible chunks, unless exceeding 80 columns significantly increases readability and does not hide information.
- Descendants are always substantially shorter than the parent and are placed substantially to the right. A very commonly used style is to align descendants to a function open parenthesis.
- These same rules are applied to function headers with a long argument list.
- Never break user-visible strings such as log messages because that breaks the ability to grep for them.
If then Else Formatting
The common approach is:
1 | if (condition) { |
If one else if
statement exists then it is usually a good idea to always have an else
block for finding not handled cases, maybe put a log message in the else even if there is no corrective action taken.
Switch Formatting
- Falling through a case statement into the next case statement shall be permitted as long as a comment is included.
- The default case should always be present and trigger an error if it should not be reached, yet is reached.
- If you need to create variables put all the codes in a block.
1 | switch (action) { |
Naming Also Declaration
Make Names Fit
Names are the heart of the programming. If the name is appropriate everything fits together naturally, relationships are clear, meaning is derivable, and reasoning from common human expectations works as expected.
So, please
- Be descriptive
- Be concise
- No Mixed Case
- No encoding type within the name
- Global variables only when necessary
- Local variables short and to the point
If you find all your names could be Thing and Dolt then you should probably revisit your design.
File Names
Filenames should be all lowercase with words separated by underscores (_
).
C files should end in .c
and header files should end in .h
. Do not use filenames that already exist in /usr/include
, such as db.h
.
In general, make your filenames very specific. For example, use http_server_logs.h
rather than logs.h
. A very common case is to have a pair of files called, e.g., foo_bar.h
and foo_bar.c
.
Function Names
Usually every function performs an action, so the name should make sure what it does.
Maybe | Better |
---|---|
error_check() |
check_for_error() |
data_file() |
dump_data_to_file() |
moving() |
is_moving() |
This also makes functions and data objects more distinguishable.
Structures and variables are often nouns. By making function names verbs and following other naming convention programs can be read more naturally.
Prefixes are sometimes useful:
- is - to ask a question about something
- get - to get a value
- set - to set a value
is_hit_retry_limit()
,get_retry_count()
,set_retry_count()
.
Variable Names
One statement per line, there should be only one statement per line unless the statements are closely related.
1 | // Don't |
Suffixes are sometimes suggested:
- max - to mean the maximum value something can have
- count - the current count of a running count variable
- key - key value
retry_max to mean the maximum number of retries, retry_count to mean the current retry count.
If a variable represents time, weight, or some other unit then include the unit in the name is a good idea.
Maybe | Better |
---|---|
int timeout |
int timeout_msecs |
int length |
int length_meters |
int weight |
int32 weight_lbs |
And always initialize all variables is good, sure it is okay to leave global variables and static variables to compiler.
Local Variable Names
- Use all lower case letters.
- Use ‘_’ as the word separator.
1 | int handle_error (int error_number) { |
Global Variable Names
Good to be prepended with a ‘g_’. Global variables should be avoided whenever possible, follow the same rules of local variable when you do need a global variable.
1 | int g_count = 0; |
Type Names
Type names are all lowercase, and good to end with a “_t” suffix, e.g.:
1 | typedef struct foo { |
If possible, do not use typedef, which hides the real type of the variable.
Structure Names
Use underscore(_
) to separate name components, use all lower case letters.
When declaring variables in structures, declare them organized use in manner to attempt to minimize memory wastage because of compiler alignment issue, then by size, and then by alphabetical order.
Don't | Better | ||||
|
|
Sometimes, it may be useful to use a meaning prefix for each member name. For example, for struct car
the prefix could be sc_brand
, sc_*
.
Major structures should be declared at the top of the file in which they are used, or in separate header files, if they are used in multiple source files.
Use of structures should be separate declarations and should be extern
if declared in a header file.
Each variable gets its own type and line, although an exception can be made when declaring bit fields (to clarify that it’s part of the one bit fields).
The use of bit fields in general is discouraged.
1 | struct foo { |
Pointer
For variables of pointer types, a good idea is to prefix the name with “p_”. Additionally, use “pp_” for pointer-to-pointer types. If you need three layers of indirection, consider restructuring your code.
Place the *
close to the variable name and the function name not pointer type.
1 | int *p_length = NULL; |
Global Constants
Global constants should be all capitalized with ‘_’ separators.
1 | const int GLOBAL_MAXIMUM_NUM = 100; |
Macro Names
Put #define
and macros
in all upper with ‘_’ separator. Macros should be capitalized, parenthesized, and should avoid side-effects.
If the macro is an expression, warp the expression in parenthesis.
If the macro is more than a single statement, use do {...} while(0)
, so that a trailing semicolon works. Right-justify the backslashes, which makes it easier to read.
1 |
|
Make Macro Name unique.
- Prepend macro names with package names is a good idea.
- Avoid simple and common names like MAX and MIN.
Enum Names
Enumeration type names should follow the general rules for types. Names are all lowercase, separated by underscores.
Labels all upper case with “_” word separators, no comma on the last element.
1 | enum pin_state_type { |
Make a label for an error state is useful to be able to say an enum is not in any of its valid states. Make a label for uninitialized or error state, make it the first label if possible.
1 | enum {STATE_ERR, STATE_OPEN, STATE_RUNNING, STATE_PAUSING, STATE_DYING}; |
Functions
- Do one thing, and do it well.
- Short, one or two screens of code.
- The idea is that each method represents a technical for archiving a single objective.
- OK to have longer function doing small different things.
- If more than 10 local variables, too complex.
Function Prototypes
In function prototypes, include parameter names with their data types. Although this is not required by the C language, it is preferred because it is a simple way to add valuable information for the reader.
Do not use the extern
keyword with function declarations as this makes lines longer and isn’t strictly necessary.
1 | /* .c file */ |
Unwritten Rules
Use code that is already present, such as:
- String functions
- Byte order functions
- Linked lists
Inline Functions
Define functions inline only when they are small, say, 10 lines or less.
You can declare functions in a way that allows the compiler to expand them inline rather than calling them through the usual function call mechanism.
Inline functions should be in a .h file. If your inline functions are very short, they should go directly into your .h file.
1 | // my_error.h |
Structures
Structure Requirements
Data structures that have visibility outside the single-threaded environment they are created and destroyed in should always have reference counts.
Reference counting means that you can avoid locking, and allows multiple users to have access to the data structure in parallel - and not having to worry about the structure suddenly going away from under them just because they slept or did something else for a while.
Remember:
“If another thread can find your data
structure, and you do not have a
reference count on it, you almost
certainly have a bug.”
Labeled Identifiers Explained
1 | struct foo { |
Old way:
1 | static struct foo bar = |
Labeled way:
1 | static struct foo bar = { |
Variables, Macro and Constants
Declare Counter Variables in For Loop
1 | // Do |
Floating-point Variables
Don’t use floating-point variables where discrete values are needed. Using a float for a loop counter is a great way to shoot yourself in the foot.
Always test floating-point numbers as <=
or >=
, never use an exact comparison (==
or !=
).
Do Not Put Data Definitions in Header Files
For example:
1 | /* |
Be Cautious With Macros
Prefer inline functions, enums, and const variables to macros.
1 |
|
Bad To Affect Control Flow in Macros
Macros that affect control flow:
1 |
Namespace Collisions in Macros
ret
is a common name for a local variable, __foo_ret
is less likely to collide with an existing variable.
1 |
Miscellaneous
No magic number
1 | if (22 == foo) |
No #ifdef in .c file
#ifdef
belongs to .h file, make it happen in .h file. Let the compiler do the work.
Before:
1 | // drivers/usb/hid_core.c |
After:
1 | // include/linux/hiddev.h |
#if is preferred not #ifdef
Use #if
MACRO not #ifdef
MACRO. Someone might write code like:
1 |
|
Always use #if
, if you have to use the preprocessor. This works fine, and does the right thing, even if DEBUG
is not defined at all (!)
1 |
|
If you really need to test whether a symbol is defined or not, test it with the defined() construct, which allows you to add more things later to the conditional without editing text that’s already in the program:
1 |
Use Header File Guard
Include files should protect against multiple inclusion through the use of macros that guard the files.
1 |
Do Not Default If Test To Nonzero
The non-zero test is often defaulted for predicates and other functions or expressions which meet the following restrictions: Returns 0 for false, nothing else.
Is named so that the meaning of a true return is absolutely obvious.
1 | // Don't |
Otherwise:
1 | // Don't |
Usually avoid Embedded Assignments
1 | a = b + c; |
Should not be replaced by
1 | d = (a = b + c) + r; |
There is a time and a place for embedded assignment statements. In some constructs there is no better way to accomplish the results without making the code bulkier and less readable.
1 | while (EOF != (c = getchar())) { |
Use of Goto, Continue, Break and ?:
The goto
statement should be used sparingly, the main place where it can be usefully employed is to break out several levels of initialization with sequence, switch, for, and while nesting with a success or failure return code.
Actually, the goto
statement is recommended only when a function exits from multiple locations and some common work such as cleanup has to be done. If there is no cleanup needed then just return directly.
Choose label names which say what the goto
does or why the goto
exists.
1 | int fun(int a) |
continue
and break
like goto
should be used sparingly as they are magic in code. Mixing continue
with break
in the same loop is a sure way to disaster.
?:
- Put the condition in parentheses
- If possible, make the action simple or a function
- Put the action for the then and else statement on a separate line unless it can be clearly put on one line.
1 | (condition) ? func1() : func2() |
Mixing C and C++
Calling C Functions from C++
1 | extern "C" int strncpy(...); |
Creating a C Function in C++
1 | extern "C" void a_c_function_in_cplusplus(int a) |
Make the C code compatible with C++ linkers.
1 |
|
Document Null Statements
Always document a null body for a for
or while
statement so that it is clear that the null body is intentional and not missing code.
1 | while (*dest++ = *src++) |
Error Return Check Policy
Check every system call for an error return, unless you know you wish to ignore errors. For example, printf returns an error code but rarely would you check for its return code. In which case you can cast the return to (void) if you really care.
Include the system error text for every system error message.
Check every call to malloc or realloc unless you know your versions of these calls do the right thing. You might want to have your own wrapper for these calls, including new, so you can do the right thing always and developers don’t have to make memory checks everywhere.
Commenting
Good to have, but must be done correctly.
Bad comments:
- Explain how code works
- Over-commenting
- Say who wrote the function
- Have last modified date
- Have other trivial things
Good comments:
- Explain what
- Explain why
- Avoid putting comments inside a function body unless small comments
Comment Style
Use either the //
or /* */
syntax, as long as you are consistent.
You can use either the //
or the /* */
syntax; however, be consistent with how you comment and what style you use where.
And please:
- Put a space next to
//
. - Put a space next to
/*
and a space before*/
.
Commenting to Closing Braces
If you do need a long statement, add comments to closing braces is good.
1 | while (condition) { |
Commenting Out Large Code Blocks
- Using
#if 0
1 | void example() |
- Use Descriptive Macro Names Instead of
#if 0
1 |
|
Include Statement Documentation
Include statements should be documented, telling the user why a particular file was included.
1 | /* |
Documentation
Comments Should Tell a Story, use a document extraction system like Doxgen.
Gotcha Keywords
@author
:
specifies the author of the module.@version
:
specifies the version of the module.@file
:
Denote this comment related to the file.@brief
:
Explain the module in brief.@param
:
specifies a parameter into a function.@return
:
specifies what a function returns.@deprecated
:
says that a function is not to be used anymore.@see
:
creates a link in the documentation to the file/function/variable to consult to get a better understanding on what the current block of code does.@todo
:
what remains to be done@bug
:
report a bug found in the piece of code
File Header
@version
is an optional item, which we usually don’t add it unless we are coding an API file.
1 | /** |
Function Header
Function header should be in the file where they are declared, which means that most likely the functions will have a header in the .h file. However, functions with no explicit prototype declaration in the .h file, should have a function header in the c file.
In common, it should be in the header file if header file exists, sometimes only in c file, such as main function.
1 | /** |
Formatting
Once the count of snippets is equaling or more than 3, for better readability more than 4 spaces are suggested from the 3rd snippet. Such as the following comment.
1 | /** |
How Do They Look Like?
1 | /** |
Tips and Tricks
VS Code
- Install
Doxygen Documentation Generator
extension. - Go to
Doxygen Comment
setting. - Click
Edit in settings.json
of'Doxdocgen>File:CopyrightTag'
, and fill in the following similar content.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"doxdocgen.file.fileOrder": [
"file",
"author",
"brief",
"empty",
"copyright",
"empty",
"custom"
],
"doxdocgen.file.copyrightTag": [
"@copyright Copyright (c) Andy Lee, {year}"
],
"doxdocgen.generic.authorTag": "@author {author} <{email}>",
"doxdocgen.generic.authorEmail": "huaqianlee@gmail.com",
"doxdocgen.generic.authorName": "Li Hua Qian"
}Open the settings.json file by
<Ctrl-P> ~/.config/Code/User/settings.json
directly can also archive the target of step 2 and 3. - More please refer to the extension ‘README’.
Sometimes some unexpected white spaces will be added by this extension,
<Ctrl-Shift-P> Trim Trailing Whitespace
orCtrl-K Ctrl-X
can remove them easily.
Vim
- Add
'huaqianlee/DoxygenToolkit.vim'
into.vimrc
to install the extension. - Add the following basic configuration into
.vimrc
.1
2
3
4
5let g:DoxygenToolkit_authorName="Li Hua Qian <huaqianlee@gmail.com>"
let g:DoxygenToolkit_yearString = strftime("%Y")
let g:DoxygenToolkit_copyrightString = "Copyright (c) Andy Lee, ".g:DoxygenToolkit_yearString
# let g:DoxygenToolkit_versionString = "0.1"
let g:DoxygenToolkit_compactDoc = "yes" :DoxAuthor
to add file header,:Dox
to add function header, more please refer to huaqianlee/DoxygenToolkit.vim.