1 排版
1.1 程序块缩进风格
程序块要采用缩进风格编写,缩进的空格数为 4 个,对齐只使用空格键,不使用 TAB 键。以免用不同的编辑器阅读程序时,因 TAB 键所设置的空格数目不同而造成程序布局不整齐。
1.2 程序中的空行
相对独立的程序块之间、变量说明之后必须加空行。
示例:如下例子不符合规范。
if (!valid_ni(ni))
{
... // program code
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
应如下书写
if (!valid_ni(ni))
{
... // program code
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
1.3 长语句多行书写
较长的语句(>80 字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied
= stat_poi[index].occupied;
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
1.4 一行一条语句
示例:如下例子不符合规范。
rect.length = 0; rect.width = 0;
应如下书写
rect.length = 0;
rect.width = 0;
1.5 if、for、do、while、case、switch、default
if、for、do、while、case、switch、default 等语句自占一行,且 if、for、do、while等语句的执行语句部分无论多少都要加括号{}。
示例:如下例子不符合规范。
if (pUserCR == NULL) return;
应如下书写:
if (pUserCR == NULL)
{
return;
}
程序块的分界符(如 C/C++语言的大括号‘{’和‘}’)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及 if、for、do、while、switch、case 语句中的程序都要采用如上的缩进方式。
示例:如下例子不符合规范。
for (...) {
... // program code
}
if (...)
{
... // program code
}
void example_fun( void )
{
... // program code
}
应如下书写。
for (...)
{
... // program code
}
if (...)
{
... // program code
}
void example_fun( void )
{
... // program code
}
1.6 代码行中的空格
(1) 关键字之后要留空格。象 const 、 virtual 、 inline 、 case 等关键字之后至少要留一个空格,否则无法辨析关键字。象 if 、 for 、 while 等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。
(2) 函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。
(3) ‘(’向后紧跟,‘)’、‘,’、‘ ; ’向前紧跟,紧跟处不留空格。
(4) ‘,’之后要留空格,如 Function(x, y, z) 。如果‘ ; ’不是一行的结束符号,其后要留空格,如 for (initialization; condition; update)。
(5) 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“ = ”、“ += ” “ >= ”、“ <= ”、“ + ”、“ * ”、“ % ”、“ && ”、“ || ”、“ << ” , “ ^ ”等二元操作符的前后应当加空格。
(6) 一元操作符如“ ! ”、“ ~ ”、“ ++ ”、“ -- ”、“ & ”(地址运算符)等前后不加空格。
(7) 像“[]”、“.”、“->”这类操作符前后不加空格。
2 注释
2.1 文件头部注释
/************************************************************************
Copyright (C), 2007-2018, xxx技术有限公司
文件名:
作者: 软件与应用研发中心 芯片驱动与测试COS开发部
版本: 1.0.0
日期: 2018.07.12
文件功能描述:
对应需求编号:
修改记录:
版本: 日期: 作者: 修改内容:
1.0.0 2014.09.11 xx 初始版本
************************************************************************/
2.2 函数头部注释
/************************************************************************
函数名:
函数功能:
输入参数:
输出参数:
返回值:
***********************************************************************/
2.3 数据结构注释
数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。
示例:可按如下形式说明枚举/数据/联合结构。
/* sccp interface with sccp user primitive message name */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND, /* sccp notify sccp user unit data come */
N_NOTICE_IND, /* sccp notify user the No.7 network can not */
/* transmission this message */
N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};
2.4 全局变量注释
全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
2.5 注释的注意事项
(1) 注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。
(2) 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
(3) 注释的内容要清楚、明了,含义准确,防止注释二义性,错误的注释不但无益反而有害。
(4) 注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。
(5) 对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。
示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
(6) 注释与所描述内容进行同样的缩排。
示例:如下例子,排版不整齐,阅读稍感不方便。
void example_fun( void )
{
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
应改为如下布局。
void example_fun( void )
{
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
(7) 通过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的。清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释。
(8) 注释格式尽量统一,建议使用“/* ⋯⋯ */”。
(9) 注释语言应该统一。出于对维护人员的考虑,建议使用中文。
3 命名规则
3.1 总体规则
(1) 函数名包含一个或者多个单词,每一个单词第一个字母大写,其他字母小写,例如Reset, SendData。
(2) 局部变量包含一个单词可以不大写,包含多个单词每一个单词第一个字母大写,其他字母小写,例如value, DataLength
(3) 全局变量和局部变量命名相同,但是需要以g_开头,例如g_value, g_DataLength。
(4) 宏要全部大写,单词之间用下划线_分开,例如
#define UART0_RSTN ((uint32_t)0x00000010)
(5) 外设寄存器结构体定义成如下形式PPP_ TypeDef,PPP为外设名字,例如
typedef struct
{
__IO uint32_t PERIOD0;
__IO uint32_t DUTY_CYCLE0;
__IO uint32_t PERIOD1;
__IO uint32_t DUTY_CYCLE1;
__IO uint32_t PERIOD2;
__IO uint32_t DUTY_CYCLE2;
__IO uint32_t PERIOD3;
__IO uint32_t DUTY_CYCLE3;
__IO uint32_t MODE_EN;
__IO uint32_t INTER;
__IO uint32_t INTER_EN;
} PWM_TypeDef;
typedef struct
{
__IO uint32_t CCR;
__IO uint32_t CNDTR;
__IO uint32_t CPAR;
__IO uint32_t CMAR;
} DMA_Channel_TypeDef;
3.2 外设函数命名
外设函数的命名以该外设的缩写加下划线为开头,例如:SPI_SendData。在函数名中,只允许存在一个下划线,用以分隔外设缩写和函数名的其它部分。
名为 PPP_Init 的函数,其功能是根据 PPP_InitTypeDef 中指定的参数,初始化外设 PPP,例如 TIM_Init.
名为 PPP_DeInit 的函数,其功能为复位外设 PPP 的所有寄存器至缺省值,例如 TIM_DeInit.
名为 PPP_StructInit 的函数,其功能为通过设置 PPP_InitTypeDef 结构中的各种参数来定义外设的功能,例如:UART_StructInit
名为 PPP_Cmd 的函数,其功能为使能或者失能外设 PPP,例如:SPI_Cmd.
名为 PPP_ITConfig 的函数,其功能为使能或者失能来自外设 PPP 某中断源,例如: RCC_ITConfig.
名为 PPP_DMAConfig 的函数,其功能为使能或者失能外设 PPP 的 DMA 接口,例如:TIM1_DMAConfig.
用以配置外设功能的函数,总是以字符串“Config”结尾,例如 GPIO_PinRemapConfig.
名为 PPP_GetFlagStatus 的函数,其功能为检查外设 PPP 某标志位被设置与否,例如:I2C_GetFlagStatus.
名为 PPP_ClearFlag 的函数,其功能为清除外设 PPP 标志位,例如:I2C_ClearFlag.
名为 PPP_GetITStatus 的函数,其功能为判断来自外设 PPP 的中断发生与否,例如:I2C_GetITStatus.
名为 PPP_ClearITPendingBit 的 函 数 , 其 功 能 为 清 除 外 设 PPP 中 断 待 处 理 标 志 位 , 例 如 : I2C_ClearITPendingBit.
4 编码规则
4.1 变量类型
建议使用stdint.h标准库中的数据类型:int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t
4.2 布尔型
typedef enum
{
FALSE = 0,
TRUE = !FALSE
} bool;
4.3 标志位状态类型
我们定义标志位类型(FlagStatus type)的 2 个可能值为“设置”与“重置”(SET or RESET)。
typedef enum
{
RESET = 0,
SET = !RESET
} FlagStatus;
4.4 功能状态类型
我们定义功能状态类型(FunctionalState type)的 2 个可能值为“使能”与“失能”(ENABLE or DISABLE)。
typedef enum
{
DISABLE = 0,
ENABLE = !DISABLE
} FunctionalState;
4.5 错误状态类型
我们错误状态类型类型(ErrorStatus type)的 2 个可能值为“成功”与“出错”(SUCCESS or ERROR)。
typedef enum
{
ERROR = 0,
SUCCESS = !ERROR
} ErrorStatus;
5 可读性
5.1 运算符优先级
注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。
示例:下列语句中的表达式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
如果书写为
high << 8 | low
a | b && a & c
a | b < c & d
由于
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不会出错,但语句不易理解;
a | b < c & d = a | (b < c) & d,(3)造成了判断条件出错。
5.2 避免使用数字
避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或宏来代替。
示例:如下的程序可读性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
... // program code
}
应改为如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... // program code
}
6 程序效率
6.2 循环体内工作量最小
应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
6.3 运用汇编
要仔细地构造或直接用汇编编写调用频繁或性能要求极高的函数。
6.4 在多重循环中,应将最忙的循环放在最内层
减少 CPU 切入循环层的次数。
示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
6.5 避免循环体内含判断语句
目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。
示例:如下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
if (data_type == RECT_AREA)
{
area_sum += rect_area[ind];
}
else
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
因为判断语句与循环变量无关,故可如下改进,以减少判断次数。
if (data_type == RECT_AREA)
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
area_sum += rect_area[ind];
}
}
else
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
6.6 尽量用乘法、位与或其它方法代替除法
例如:remainder = count % 16;
可以改为remainder = count & 15;
7 声明与定义
7.1 函数
函数应当具有原型声明,原型的使用使得编译器能够检查函数定义和调用的完整性。如果没有原型,就不会迫使编译器检查出函数调用当中的一定错误(比如,函数体具有不同的参数数目,调用和定义之间参数类型的不匹配)。事实证明,函数接口是相当多问题的肇因,因此本规则是相当重要的。
对外部函数来说,我们建议采用如下方法,在头文件中声明函数(亦即给出其原型),并在所有需要该函数原型的代码文件中包含这个头文件。
为具有内部链接的函数给出其原型也是良好的编程实践。如果一个函数只是在同一文件中的其他地方调用,那么就用 static。使用 static 存储类标识符将确保标识符只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。
7.2 全局变量
(1) 良好的编程实践是,在不必要的情况下避免使用全局变量。
(2) 全局变量如果作用域只是在同一文件中的其他地方调用,就用 static限定其作用域。
(3) 全局变量如果作用域是多个文件,定义写在xxx.c文件中,声明写在对应的xxx.h文件中,并且用extern 关键字声明。
8 代码编辑、编译、审查
(1) 打开编译器的所有告警开关对程序进行编译。对编译器报出的警告要认真对待,尽量消除所有的警告。
(2) 在产品软件(项目组)中,要统一编译开关选项。
(3) 通过代码走读及审查方式对代码进行检查。
(4) 代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行。
(5) 测试部测试产品之前,应对代码进行抽查及评审。
(6) 编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。