-
Gururaj Shetty authoredGururaj Shetty authored
C Coding Style Guide
Purpose
Rules are not perfect. Disabling useful features in specific situations may affect code implementation. However, the rules are formulated “to help most programmers to get more benefits”. If a rule is found unhelpful or difficult to follow in team coding, please send your feedback to us so we can improve the rule accordingly. Before referring to this guide, you are expected to have the following basic capabilities for C rather than being a beginner who wants to learn about C.
- Understand the ISO standard of C.
- Be familiar with the basic features of C.
- Understand the standard library of C.
General Principles
Code must meet the requirements for readability, maintainability, security, reliability, testability, efficiency, and portability while ensuring functionality correctness.
Conventions
It is necessary to understand the reason for these conventions and to try and comply with them, no matter if they are rules or recommendations.
Exceptions
Naming
Names include file, function, variable, type, and macro names.
The unified naming style is the most direct expression of the consistency principle.
General Conventions
Rule 1.1 Name identifiers in camel case style.
Type | Naming Style |
---|---|
Function, struct, enumeration, union | UpperCamelCase |
Variable, function parameter, macro parameter, struct body, union member | lowerCamelCase |
Macro, constant, enumerated value, goto tag | All capitalized, separated with underscores (_) |
Note
- The
constant
in the above table refers to the variable of the basic data type, enumeration, and string type of the const modifier under the global scope, excluding array, struct and union. - The
variable
in the above table refers to variables other than the constant definition, all using lowercase.
Rec 1.1 The larger the scope, the more accurate the name should be.
Example:
int GetCount(void); // Bad: inaccurate description.
int GetActiveConnectCount(void); // Good
int PrefixFuncName(void); // OK: CamelCase, with no prefix in the format, but prefix in the content.
enum XxxMyEnum { // OK.
...
};
File Naming
Rec 1.2 Use lowercase file names.
dhcp_user_log.c
dhcp_user-log.c
: It is not recommended you separate names with
‘-’.dhcpuserlog.c
: The words are not separated, causing poor
readability.Function Naming
Functions are named in UpperCamelCase style.
Rec 1.3 Name functions that comply with reading habits.
The “verb + object” structure can be used for action related function names. For example:
AddTableEntry() // OK
DeleteUser() // OK
GetUserInfo() // OK
An adjective or a prefix “is” can be used in a function returning a Boolean value. For example:
DataReady() // OK
IsRunning() // OK
JobDone() // OK
Data or Getter function:
TotalCount() // OK
GetTotalCount() // OK
Variable Naming
Variables are named in the lowerCamelCase style. This includes global variables, local variables, parameters in the function declaration or definition as well as parameters in function-like macro.
Rule 1.2 Add the ‘g_’ prefix to global variables. Do not add this prefix to static variables in a function.
int g_activeConnectCount;
void Func(void)
{
static int pktCount = 0;
...
}
Notes: The nature of a constant is also a global variable, but it does not apply to current rule if the naming style is all uppercase and connected by underline.
Rec 1.4 Keep local variables short and to the point.
The name of a local variable should be short on the premise that meanings can be expressed through context.
The following is an example:
int Func(...)
{
enum PowerBoardStatus powerBoardStatusOfSlot; // Not good: Long redundant local variable.
powerBoardStatusOfSlot = GetPowerBoardStatus(slot);
if (powerBoardStatusOfSlot == POWER_OFF) {
...
}
...
}
Better writing style:
int Func(...)
{
enum PowerBoardStatus status; // Good: The status can be clearly expressed in context.
status = GetPowerBoardStatus(slot);
if (status == POWER_OFF) {
...
}
...
}
int i;
...
for (i = 0; i < COUNTER_RANGE; i++) {
...
}
Or, variables in simple math functions:
int Mul(int a, int b)
{
return a * b;
}
Type Naming
Example:
struct MsgHead {
enum MsgType type;
int msgLen;
char *msgBuf;
};
union Packet {
struct SendPacket send;
struct RecvPacket recv;
};
enum BaseColor {
RED, // Note: The enumeration is in UpperCamelCase style while the enumerated values adopt the macro naming style.
GREEN,
BLUE
};
typedef int (*NodeCmpFunc)(struct Node *a, struct Node *b);
typedef struct { // Good: The anonymous struct is used because self-nesting is not required.
int a;
int b;
} MyType; // Struct alias with UpperCamelCase.
```c
typedef struct tagNode { // Good: Add the 'tag' prefix or use 'Node_'.
struct tagNode *prev;
struct tagNode *next;
} Node; // UpperCamelCase.
Macros, Constants, and Enumeration Naming
Macro example:
#define PI 3.14
#define MAX(a, b) (((a) < (b)) ? (b) : (a))
#ifdef SOME_DEFINE
void Bar(int);
#define Foo(a) Bar(a) // The function-like macro is named as a function style.
#else
void Foo(int);
#endif
Constant example:
const int VERSION = 200; // OK.
const enum Color DEFAULT_COLOR = BLUE; // OK.
const char PATH_SEP = '/'; // OK.
const char * const GREETINGS = "Hello, World!"; // OK.
Non-constant example:
// Structure type does not meet the definition of constant.
const struct MyType g_myData = { ... }; // OK: Name it in lowerCamelCase style.
// Array type does not meet the definition of constant.
const int g_xxxBaseValue[4] = { 1, 2, 4, 8 }; // OK: Name it in lowerCamelCase style.
int Foo(...)
{
// The scope does not meet the definition of constant.
const int bufSize = 100; // OK: Name it in lowerCamelCase style.
...
}
Enumeration example:
// Note: The enumeration type name is in the UpperCamelCase style, while enumerated values are all capitalized and separated with underscores (_).
enum BaseColor {
RED,
GREEN,
BLUE
};
Rec 1.5 Do not name temporary variables in function-like macros and pollute the external scope.
First, use function-like macros as little as possible.
When a function-like macro needs to define local variables, to avoid naming conflicts with local variables in external functions,
an underline is a good solution. Example:
#define SWAP_INT(a, b) do { \
int tmp_ = a; \
a = b; \
b = tmp_; \
} while (0)
Formatting
Line Length
Rec 2.1 Ensure that each line is no more than 120 characters in length.
The following scenarios should not be wrapped, and can be treated as exceptions:
- Line breaks can cause strings truncated and hard to retrieved (grep), such as command lines or URLs. Codes or comments that contain these can be treated as exceptions appropriately.
- ‘#include’, ‘#error’ statements are allowed to exceed the line width requirement, but you should try to avoid this.
Example:
#ifndef XXX_YYY_ZZZ
#error Header aaaa/bbbb/cccc/abc.h must only be included after xxxx/yyyy/zzzz/xyz.h
#endif
Indentation
Rule 2.1 Use spaces to indent and indent four spaces at a time.
Braces
Rule 2.2 Use the K&R indentation style.
For example:
struct MyType { // Good: Follow the statement to the end, and indent one space.
...
}; // Good: The right brace is followed by the semicolon.
int Foo(int a)
{ // Good: The left brace of the function starts a new line, and nothing else is placed on the line.
if (...) {
...
} else { // Good: The 'else' statement follows the right brace.
...
} // Good: The right brace takes a single line.
}
Function Declaration and Definition
Rule 2.3 The return type and function name of the function declaration and definition must be on the same line. The function parameter list must be aligned appropriately if it needs to be wrapped.
When a function is declared and defined, the return value type of the function should be in the same line as the function name.
The following is an example of line breaks:
ReturnType FunctionName(ArgType paramName1, ArgType paramName2) // Good:All in one line
{
...
}
ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1, // The line length cannot accommodate all parameters and thus a line break is required.
ArgType paramName2, // Good:Aligned with the previous line.
ArgType paramName3)
{
...
}
ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // Subject to line length, a line break is required.
ArgType paramName3, ArgType paramName4, ArgType paramName5) // Good: After the line break, 4 spaces are used for indentation.
{
...
}
ReturnType ReallyReallyReallyReallyLongFunctionName( // The line length cannot accommodate the first parameter, and thus a line break is required.
ArgType paramName1, ArgType paramName2, ArgType paramName3) // Good: After the line break, 4 spaces are used for indentation.
{
...
}
Function Calls
Rule 2.4 The parameter list should be aligned appropriately when the parameter list requires a line break.
The following is an example of line breaks:
ReturnType result = FunctionName(paramName1, paramName2); // Good:Function parameters are placed in one line.
ReturnType result = FunctionName(paramName1,
paramName2, // Good:Aligned with the above parameters.
paramName3);
ReturnType result = FunctionName(paramName1, paramName2,
paramName3, paramName4, paramName5); // Good:After the line break, 4 spaces are used for indentation.
ReturnType result = VeryVeryVeryLongFunctionName( // The line length cannot accommodate the first parameter, and thus a line break is required.
paramName1, paramName2, paramName3); // After the line break, 4 spaces are used for indentation.
If the parameters in a function call are associated with each other, the parameters are grouped for better understanding, rather than strictly adhering to formatting requirements.
// Good:The parameters in each line represent a group of data structures with a strong correlation. They are placed on one line for ease of understanding.
int result = DealWithStructureLikeParams(left.x, left.y, // Indicates a group of parameters.
right.x, right.y); // Indicates another group of related parameters.
Conditional Statements
Rule 2.5 Conditional statements must use braces.
- Logic is intuitive and easy to read.
- It is not easy to make mistakes when adding new code to the existing conditional statement.
- Function-like macros without braces are used in conditional statements, can be error prone if braces do not surround the conditional statement.
if (objectIsNotExist) { // Good: Braces are added to a single-line conditional statement.
return CreateNewObject();
}
Rule 2.6 Do not place ‘if’, ‘else’ and ‘else if’ in the same line.
In a conditional statement, if there are multiple branches, they should be written in different lines.
The correct format:
if (someConditions) {
...
} else { // Good: The 'else' is in a different line of 'if'.
...
}
The following is a case that does not comply with the specifications:
if (someConditions) { ... } else { ... } // Bad: They are in the same line.
Loops
Rule 2.7 Use braces in loop statements.
Similar to the condition expression, we require that the for and while loop conditional statements contain braces, even if there is only one loop.
for (int i = 0; i < someRange; i++) { // Good: Braces are used.
DoSomething();
}
while (condition) { } // Good: The while loop body is empty. And braces are used.
while (condition) {
continue; // Good: The continue keyword highlights the end of the empty loop. And braces are used.
}
Bad example:
for (int i = 0; i < someRange; i++)
DoSomething(); // Bad: Braces should be added.
while (condition); // Bad: Using semicolons will make people misunderstand which code is a part of the while statement.
Switch Statements
Rule 2.8 Indent the case or default statement in a switch statement block.
The indentation style of the switch statement is as follows:
switch (var) {
case 0: // Good: Indented
DoSomething1(); // Good: Indented
break;
case 1: { // Good: Braces are added.
DoSomething2();
break;
}
default:
break;
}
switch (var) {
case 0: // Bad: 'case' not indented
DoSomething();
break;
default: // Bad: 'default' not indented
break;
}
Expressions
Rec 2.2 Keep a consistent expression line break style and ensure that operators are placed at the end of the line.
Example:
// Pretend that the following first line does not meet the line length requirement.
if ((currentValue > MIN) && // Good: After the line break, the Boolean operator is placed at the end of the line.
(currentValue < MAX)) {
DoSomething();
...
}
int result = reallyReallyLongVariableName1 + // Good: The plus sign is placed at the end of the line.
reallyReallyLongVariableName2;
After an expression is wrapped, ensure that the lines are properly aligned or indented by 4 spaces. See the following example.
int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
longVaribleName4 + longVaribleName5 + longVaribleName6; // OK: indented with 4 spaces
int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
longVaribleName4 + longVaribleName5 + longVaribleName6; // OK: aligned
Variable Assignment
Rule 2.9 Multiple variable definitions and assignment statements cannot be written on one line.
It is best to have only one variable initialization statement on each line. It is easier to read and understand.
int maxCount = 10;
bool isCompleted = false;
The following is an example that does not comply with the specifications:
int maxCount = 10; bool isCompleted = false; // Bad: Multiple initialization statements are placed on the same line.
int x, y = 0; // Bad: Variable definitions need to be placed on different lines. Each definition occupies one line.
int pointX;
int pointY;
...
pointX = 1; pointY = 2; // Bad: Multiple variable assignment statements are placed on the same line.
int i, j; // Good: Multiple variables that are defined and do not need to be initialized immediately can be written in one line.
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
...
}
}
Initialization
Initialization is applicable to structs, unions, and arrays.
Rule 2.10 Indent when initiating a new line, or make a reasonable alignment.
// Good: No line break for a short line.
int arr[4] = { 1, 2, 3, 4 };
// Good: A line break here makes the readability better.
const int rank[] = {
16, 16, 16, 16, 32, 32, 32, 32,
64, 64, 64, 64, 32, 32, 32, 32
};
int a[][4] = {
{ 1, 2, 3, 4 }, { 2, 2, 3, 4 }, // OK.
{ 3, 2, 3, 4 }, { 4, 2, 3, 4 }
};
int b[][8] = {
{ 1, 2, 3, 4, 5, 6, 7, 8 }, // OK.
{ 2, 2, 3, 4, 5, 6, 7, 8 }
};
int c[][8] = {
{
1, 2, 3, 4, 5, 6, 7, 8 // OK.
}, {
2, 2, 3, 4, 5, 6, 7, 8
}
};
Note:
- If the left brace is placed at the end of the line, the corresponding right brace shoud be placed into a new line.
- If the left brace is followed by the content, the corresponding right brace should also follow the content.
Rule 2.11 When struct and union members are initialized, each member is initialized on a separate line.
The C99 standard supports the initialization of the struct and union members in their definition. This is called the designated initializer. If initialization is performed in this way, each member is initialized in a separate line.
struct Date {
int year;
int month;
int day;
};
struct Date date = { // Good: When the designated initializer is used, each member is initialized on a separate line.
.year = 2000,
.month = 1,
.day = 1
};
Pointers
Rec 2.3 The pointer type asterisk “*” follows the variable name or the type. Do not leave spaces on both sides and always use at least one space.
When you declare or define a pointer variable or return a pointer type function, “*” can be placed on the left or right, adhering to the type or name. Do not leave spaces on both sides. And do not leave out spaces altogether.
int *p1; // OK.
int* p2; // OK.
int*p3; // Bad: No spaces.
int * p4; // Bad: Spaces on both sides.
Choose a style and stay consistent.
When using style that “*” follows type, avoid declaring multiple variables with pointer in a line.
int* a, b; // Bad: It is easy to misinterpret b as a pointer.
char * const VERSION = "V100"; // OK.
int Foo(const char * restrict p); // OK.
“*” never follows ‘const’ or ‘restrict’ keywords anytime.
Compilation Preprocessing
Rule 2.12 The number sign (#) must be placed at the beginning of a line for compilation preprocessing and can be indented in nested compilation preprocessing.
The number sign (#) must be placed at the beginning of a line for compilation preprocessing, even if the code is embedded in the function body.
#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) // Good: "#" is at the beginning of the line.
#define ATOMIC_X86_HAS_CMPXCHG16B 1 // Good: "#" is at the beginning of the line.
#else
#define ATOMIC_X86_HAS_CMPXCHG16B 0
#endif
int FunctionName(void)
{
if (someThingError) {
...
#ifdef HAS_SYSLOG // Good: "#" is at the beginning of the line even though it's in a function body.
WriteToSysLog();
#else
WriteToFileLog();
#endif
}
}
Nested preprocessing statements starting with “#” can be indented and aligned based on indentation requirements to different layers.
#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
#define ATOMIC_X86_HAS_CMPXCHG16B 1 // Good: Statements are layered, facilitating reading.
#else
#define ATOMIC_X86_HAS_CMPXCHG16B 0
#endif
Whitespace
Rule 2.13 Ensure that horizontal whitespace is used to highlight keywords, important information and avoid unnecessary whitespace.
Horizontal spaces should be used to highlight keywords and important information. Do not add spaces at the end of each line of code. The general rules are as follows:
- Add spaces after keywords such as if, switch, case, do, while, and for.
- Do not add spaces after the left parenthesis or before the right parenthesis.
- Add a space before and after each binary operator (= + - < > * / % | & ^ <= >= == !=).
- Do not add a space after any unary operator (& * + - ~!).
- A space is required before and after each ternary operator (? :).
- Add spaces before and after the colon of bit field description.
- There is no space between ++/– and variables.
- There is no space before and after the struct member operator (. ->).
- Adding or not adding spaces inside the brace must be consistent.
- Do not add spaces before commas, semicolons, colons (without the colon in the ternary operator or the bit field description); Add spaces after them.
- There is no space between the parentheses of the function parameter list and the function name.
- There is no space between the parenthesis of the type cast and the object being converted.
- There is no space between the square bracket of the array and the array name.
- Spaces at the end of the line can be omitted.
For spaces inside the braces, the following are recommended:
- In general, spaces should be added after the left brace or before the right brace.
- For empty, or a single identifier, or a single literal constant, spaces are not required. Such as: ‘{}’, ‘{0}’, ‘{NULL}’, ‘{“hi”}’.
- Spaces between consecutively nested multiple parentheses, spaces are not required. Such as: ‘{{0}}’, ‘{{ 1, 2 }}’. Bad example: ‘{ 0, {1}}’. It is not a continuous nested scene, and the spaces inside the outermost braces are inconsistent.
In normal cases:
int i = 0; // Good: When the variable is initialized, there should be spaces before and after the =. Do not leave a space before the semicolon.
int buf[BUF_SIZE] = {0}; // Good: During array initialization, spaces in curly braces are optional.
int arr[] = { 10, 20 }; // Good: A space is added before and after the brace.
Function definition and call:
int result = Foo(arg1,arg2);
^ // Bad: There should be a space after the comma.
int result = Foo( arg1, arg2 );
^ ^ // Bad: No space should be added to either side in the parentheses.
Pointer and address-of operator:
x = *p; // Good: There is no space between the operator * and the pointer p.
p = &x; // Good: There is no space between the operator & and the variable x.
x = r.y; // Good: When a member variable is accessed through the operator (.), no space is added.
x = r->y; // Good: When a member variable is accessed through the operator (.), no space is added.
Operator:
x = 0; // Good: A space must be added before and after the assignment operator (=).
x = -5; // Good: Do not add spaces before the minus sign (-) and the number.
++x; // Good: Do not add spaces before the minus sign (-) and the number.
x--;
if (x && !y) // Good: A space must be added before and after the Boolean operator. Do not leave spaces between the ! operator and variables.
v = w * x + y / z; // Good: A space must be added before and after binary operators.
v = w * (x + z); // Good: No space is required before and after the expression in the parentheses.
Loops and conditional statements:
if (condition) { // Good: A space is added between the if keyword and the parenthesis and no space is added before or after the conditional statement inside the parentheses.
...
} else { // Good: A space is added between the else keyword and the curly brace.
...
}
while (condition) {} // Good: A space is added between the while keyword and the parenthesis. No space is added before or after the conditional statement inside the parentheses.
for (int i = 0; i < someRange; ++i) { // Good: A space is added between the for keyword and the parenthesis, and after the semicolons (;).
...
}
switch (var) { // Good: A space is added after the switch keyword.
case 0: // Good: No space is added between the case conditional statement and the colon.
...
break;
...
default:
...
break;
}
Note: Current integrated development environments (IDEs) and code editors can be set to automatically delete spaces at the end of a line. Please configure your editor as such.
Rec 2.4 Arrange blank lines reasonably keep the code compact.
Reduce unnecessary blank lines so that more code can be displayed for easy reading. The following rules are recommended:
- Make a reasonable arrangement of blank lines according to the degree of relevance of neighboring content.
- Do not put two or more consecutive blank lines inside a function, a type definition, a macro, or an initialization expression.
- Do not use three or more consecutive blank lines.
- Do not add blank lines at the start and end of a code block defined by braces.
ret = DoSomething();
if (ret != OK) { // Bad: Return value judgment should follow the function call.
return -1;
}
int Foo(void)
{
...
}
int Bar(void) // Bad: Use no more than two continuous blank lines.
{
...
}
int Foo(void)
{
DoSomething(); // Bad: The blank lines above and below are unnecessary.
...
}
Comments
The comments must be concise, clear, and unambiguous, ensuring that the information is complete and not redundant.
Comment code in fluent English.
Comment Style
/*
*/
and //
can be used.Note: The sample code used in this article sees extensive use of the ‘//’ post-comment. This is only to aid understanding, and does not mean that this comment style is better.
File Header Comments
Rule 3.1 File header comments must contain the copyright license.
- Copyright (c) 2020 Huawei Device Co., Ltd.
- Licensed under the Apache License, Version 2.0 (the “License”);
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an “AS IS” BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License. */
Function Header Comments
Rule 3.2 Empty function header comments with no content are forbidden.
// Single-line function header
int Func1(void);
// Multi-line function header
// Second line
int Func2(void);
Use ‘/*’ ‘*/’ to start the function header.
/* Single-line function header */
int Func1(void);
/*
* Single-line or multi-line function header
* Second line
*/
int Func2(void);
Example:
/*
* The number of written bytes is returned. If -1 is returned, the write operation fails.
* Note that, the memory buffer is released by the caller.
*/
int WriteString(char *buf, int len);
Bad example:
/*
* Function name: WriteString
* Function: Write a character string.
* Parameter:
* Return value:
*/
int WriteString(char *buf, int len);
Problems in the preceding example are as follows:
- The ‘Parameter’ and ‘Return value’ headings have no content.
- The function name has redundant information.
- The most important thing, the notice to release the buffer, is not clearly stated.
Code Comments
Rule 3.3 Code comments are placed above or to the right of the corresponding code.
Rule 3.4 There must be a space between the comment character and the comment content. At least one space is required between the comment and code if the comment is placed to the right of code.
// Single-line comment
DoSomething();
// Multi-line comment
// Second line
DoSomething();
Use ‘/*’ ‘*/’ to start the comment.
/*Single-line comment */
DoSomething();
/*
* Single-/Multi-line comment
* Second line
*/
DoSomething();
Select and use one of the following styles:
int foo = 100; // Comment on the right
int bar = 200; /* Comment on the right */
#define A_CONST 100 /* Related comments of the same type can be aligned vertically. */
#define ANOTHER_CONST 200 /* Leave spaces after code to align comments vertically. */
If the comment on the right exceeds the permitted line length, the comment can be placed above the code.
Rule 3.5 Delete unused code segments. Do not comment them out.
Here, commenting out refers to the removal of code from compilation without actually deleting it. This is done by using /* */, //, #if 0, #ifdef NEVER_DEFINED, and so on.
Rec 3.1 No TODO, TBD, or FIXME comment is allowed in code delivered to a customer.
// TODO(<author-name>): Supplement to XX
// FIXME: XX defect
During version development, this type of comments can be used for highlighting, and must be processed and deleted before delivery.
Rec 3.2 If ‘break’ or ‘return’ is not added to the end of the case statement block (fall-through), comments must be provided.
For example, explicitly specify the “fall-through”:
switch (var) {
case 0:
DoSomething();
/* fall-through */
case 1:
DoSomeOtherThing();
...
break;
default:
DoNothing();
break;
}
If the case statement is empty, the comment explaining the “fall-through” can be omitted.
switch (var) {
case 0:
case 1:
DoSomething();
break;
default:
DoNothing();
break;
}
Header Files
This chapter summarizes some methods from the perspective of programming specifications to help you properly plan header files.
Header File Responsibility
Rec 4.1 Each .c file must have a corresponding .h file, which is used to declare the interfaces that need to be disclosed externally.
Exception: the entry point of the program (for example, the file where the main function is located), unit test code, and dynamic library code.
#ifndef FOO_H
#define FOO_H
int Foo(void); // Good: Declare an external interface in the header file.
#endif
foo.c content
static void Bar(void); // Good: The declaration of the internal function is placed in the header of the .c file, declaring its static scope.
void Foo(void)
{
Bar();
}
static void Bar(void)
{
// Do something;
}
Internally used functions declarations, macros, enumerations, structures, and others should not be placed in header files.
Rec 4.2 Use .h as the extension of the header file, rather than other unconventional extensions, for example, .inc.
Some products use .inc as the header file name extension, which does not comply with the C language. A header file using .inc as the file name extension usually indicates a private header file. However, in practice, this recommendation is not followed properly. An .inc file is generally contained in multiple .c files. This document does not recommend that private definitions be stored in header files. For details, see Rec 4.1.
Header File Dependency
Dependency direction is as follows: Products depend on the platform, and the platform depends on the standard library.
Rule 4.1 Forbid cyclic dependency of header files.
The cyclic dependency of header files reflects an obviously unreasonable architecture design, which can be avoided through optimization.
Rule 4.2 The header file must have the internal #include protection character (#define protection).
To prevent header files from being included multiple times, all header files should be protected by #define. Do not use #pragma once.
When defining a protection character, comply with the following rules:
- The protection character uses a unique name. It is recommended to consider the file path and name below the top layer of the project code tree.
- Do not place code or comments before or after the protected part, except for file header comments.
Assume that the path to timer.h of the timer module is
timer/include/timer.h
. If the protection character resembles
‘TIME_H’, it is not unique. Add a path, for example:
#ifndef TIMER_INCLUDE_TIMER_H
#define TIMER_INCLUDE_TIMER_H
...
#endif
Rule 4.3 Do not reference external function interfaces and variables by using declaration.
extern int Foo(void); // Bad: Reference external functions by using the extern declaration.
void Bar(void)
{
int i = Foo(); // Here, the external interface Foo is used.
...
}
#include "b.h" // Good: Use the interface providing the interface.
void Bar(void)
{
int i = Foo();
...
}
b.h content
int Foo(void);
b.c content
int Foo(void)
{
// Do something
}
Rule 4.4 Do not include header files in extern “C”.
If a header file is included in extern “C”, extern “C” may be nested. Some compilers restrict the nesting of extern “C”. If there are too many nested layers, compilation errors may occur.
extern “C” usually occurs in mixed programming using both C and C++. If the extern “C” includes a header file, the original intent behind the header file may be hindered. For example, when the linkage specifications are modified incorrectly.
...
#ifdef __cplusplus
void Foo(int);
#define A(value) Foo(value)
#else
void A(int)
#endif
b.h content
...
#ifdef __cplusplus
extern "C" {
#endif
#include "a.h"
void B(void);
#ifdef __cplusplus
}
#endif
Using the C++ preprocessor to expand b.h, the following information is displayed:
extern "C" {
void Foo(int);
void B(void);
}
In the a.h file, the function Foo is intended to be a C++ free function
following the C++ specifications. However, in the b.h file, because
#include "a.h"
is placed inside extern "C"
, the linking
specification of function Foo is changed incorrectly.
Exception: In the C++ compilation environment, if you want to reference
a header file written in pure C, a non-intrusive approach is to exclude
the C header file from extern "C"
.
Functions
Functions help avoid repeated code and increase reusability. In addition, functions act to layer code and reduce code complexity, hiding implementation details, making programs more modular, and facilitating reading and maintenance.
Function Design
Writing clean functions and organizing code effectively is the essence of good function design. The code should be simple and not conceal the designer’s intention, using clean abstractions and straightforward control statements to organize the function naturally.
Rule 5.1 Avoid long functions and ensure that functions contain no more than 50 lines (not including blank lines and comments).
A function should fit on one screen, (be no longer than 50 lines), do only one thing, and do it well.
Large functions are often caused by the fulfillment of more than one purpose by the function, or over complication when parts could be abstracted.
Rule 5.2 Avoid nesting a code block more than four times within a function.
guardian statements
, (a short conditional return), can
effectively reduce the nesting layers of if statements. For example:int Foo(...)
{
if (received) {
type = GetMsgType(msg);
if (type != UNKNOWN) {
return DealMsg(...);
}
}
return -1;
}
Refactored code using the guardian statement
, the nesting level is
2:
int Foo(...)
{
if (!received) { // Good: Use the 'guardian statement'.
return -1;
}
type = GetMsgType(msg);
if (type == UNKNOWN) {
return -1;
}
return DealMsg(..);
}
Rec 5.1 Process all returned error codes.
A function (in a standard library, a third-party library, or a user-defined function) must be able to indicate errors. This can be done by using error tags, special return data, or other means. No matter when a function provides such a mechanism, the caller should immediately check the error indication after the function returns.
Example:
char fileHead[128];
ReadFileHead(fileName, fileHead, sizeof(fileHead)); // Bad: The returned value is not checked.
DealWithFileHead(fileHead, sizeof(fileHead)); // The 'fileHead' is possibly invalid.
The correct format is as follows:
char fileHead[128];
ret = ReadFileHead(fileName, fileHead, sizeof(fileHead));
if (ret != OK) { // Good: Ensure that the 'fileHead' is written.
return ERROR;
}
DealWithFileHead(fileHead, sizeof(fileHead)); // Process the file header.
void
.Function Parameters
Rec 5.2 Use the return value instead of the output parameter when designing a function.
Using return values rather than output parameters improves readability and usually provides the same or better performance.
Readability can be improved by naming functions such as GetXxx, FindXxx, or directly using a single noun, to directly return the corresponding object.
Rec 5.3 Use strongly typed parameters and avoid using void*
Strong types help the compiler find more errors for us. Pay attention to
the usage of the FooListAddNode
function in the following code:
struct FooNode {
struct List link;
int foo;
};
struct BarNode {
struct List link;
int bar;
}
void FooListAddNode(void *node) // Bad: Here, the void * type is used to pass parameters.
{
FooNode *foo = (FooNode *)node;
ListAppend(&g_fooList, &foo->link);
}
void MakeTheList(...)
{
FooNode *foo;
BarNode *bar;
...
FooListAddNode(bar); // Wrong: In this example, the foo parameter was supposed to be passed, but the bar parameter is incorrectly passed instead. No error is reported immediately and issues may occur as a result.
}
FooListAddNode
is specified clearly,
instead of with the void *
type, the preceding problem can be
detected during compilation.void FooListAddNode(FooNode *foo)
{
ListAppend(&g_fooList, &foo->link);
}
Exception: For some generic interfaces, you can use the input parameter
void *
to pass different types of pointers.
It is the caller’s responsibility to check the validity of internal function parameters of a module.
int SomeProc(...)
{
int data;
bool dataOK = GetData(&data); // Get data.
if (!dataOK) { // Check the result of the previous step ensuring data validity.
return -1;
}
DealWithData(data); // Call the data processing function.
...
}
void DealWithData(int data)
{
if (data < MIN || data > MAX) { // Bad: The caller has already ensured the validity of the data.
return;
}
...
}
Rec 5.5 The pointer argument of a function should be declared as ‘const’ if it is not used to modify the pointed object.
The const pointer argument, which restricts the function from modifying the object through the pointer, makes code stronger and safer.
Example: In the example of the strncmp in the 7.21.4.4 of the C99 standard, the invariant parameter is declared as const.
int strncmp(const char *s1, const char *s2, size_t n); // Good: The invariant parameter is declared as const.
Note: Whether or not the pointer parameter is to be declared as ‘const’ depends on the function design, not on whether there is a “modify object” action in the function entity.
Rec 5.6 Ensure that the number of parameters in a function is less than or equal to 5.
If a function has too many parameters, the function is easy to be affected by changes in external code, hindering maintenance. Too many function parameters will also increases the workload for testing.
The number of parameters in a function must not exceed 5. If the number of parameters exceeds 5, consider the following: