一. 变量
1. 一个变量只有一个功能,不能把一个变量用作多种用途
说明:一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。
示例:具有两种功能的反例
uint8_t getData(void) { uint8_t data; data = 3; data = get(data); /* data具有两种功能:位置和函数get的返回值 */ return data; }
正确做法:使用两个变量
uint8_t getData(void) { uint8_t num; uint8_t data; num = 3; data = get(data); return data; }
2. 结构功能单一;不要设计面面俱到的数据结构;
说明:有关联的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不是一组相关性不强的数据的集合。
设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。
3. 不用或者少用全局变量;
说明:单个文件内部可以使用static的全局变量,可以将其理解为类的私有成员变量。
全局变量应该是模块的私有数据,不能作用对外的接口使用,使用static类型定义,可以有效防止外部文件的非正常访问。
4. 防止局部变量与全局变量同名;
说明:尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。
5. 严禁使用未经初始化的变量作为右值;
说明:可以避免未初始化错误。
6. 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象;
7. 在首次使用前初始化变量,初始化的地方离使用的地方越近越好;
说明:未初始化变量是C和C++程序中错误的常见来源。在变量首次使用前确保正确初始化。在较好的方案中,变量的定义和初始化要做到亲密无间。
8. 明确全局变量的初始化顺序,避免跨模块的初始化依赖;
说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚。
9. 尽量减少没有必要的数据类型默认转换与强制转换;
说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。
二. 宏、常量
1. 用宏定义表达式时,要使用完备的括号;
说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。
示例:如下定义的宏都存在一定的风险
#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)
正确的定义应为:
#define RECTANGLE_AREA(a, b) ((a) * (b))
这是因为:
如果定义#define RECTANGLE_AREA(a, b) a * b 或#define RECTANGLE_AREA(a, b) (a * b)
则c/RECTANGLE_AREA(a, b) 将扩展成c/a * b , c 与b 本应该是除法运算,结果变成了乘法运算,造成错误。
如果定义#define RECTANGLE_AREA(a, b) (a) * (b)
则RECTANGLE_AREA(c + d, e + f)将扩展成:(c + d * e + f), d与e 先运算,造成错误。
2. 将宏所定义的多条表达式放在大括号中
说明:更好的方法是多条语句写成do while(0)的方式。
示例:看下面的语句,只有宏的第一条表达式被执行。
#define FOO(x) \ printf("arg is %d\n", x); \ do_something_useful(x);
为了说明问题,下面for语句的书写稍不符规范
for (blah = 1; blah < 10; blah++) FOO(blah)
用大括号定义的方式可以解决上面的问题:
#define FOO(x) { \ printf("arg is %s\n", x); \ do_something_useful(x); \ }
但是如果有人这样调用:
if (condition == 1) FOO(10); else FOO(20);
那么这个宏还是不能正常使用,所以必须这样定义才能避免各种问题:
#define FOO(x) do { \ printf("arg is %s\n", x); \ do_something_useful(x); \ } while(0)
用do-while(0)方式定义宏,完全不用担心使用者如何使用宏,也不用给使用者加什么约束。
3. 使用宏时,不允许参数发生变化;
4. 不允许直接使用魔鬼数字;
5. 除非必要,应尽可能使用函数代替宏;
说明:宏对比函数,有一些明显的缺点:①. 宏缺乏类型检查,不如函数调用检查严格;②. 以宏形式写的代码难以调试难以打断点,不利于定位问题;③. 宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高;
6. 常量建议使用const定义代替宏;
说明:当报错时,只会显示常量,不会显示宏定义的名字,查找时,很费劲。
7. 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句;
说明:如果在宏定义中使用这些改变流程的语句,很容易引起资源泄漏问题,使用者很难自己察觉。