前言
标识符,包括变量名、函数名、类型名等,是编程语言中用来区分和引用程序元素的基本构造单元。良好的标识符命名实践是确保代码可读性、可维护性和可靠性的关键因素之一。本文基于MISRA-C手册进行学习和总结标识符相关规则。文末附参考文献地址。
规则 5.1(必须): 标识符(内部和外部)不应依赖于超过31个字符的意义
解释: 此规则要求内部标识符(即在文件内部定义的标识符)必须在其前31个字符内保持唯一性,以确保代码的可移植性。该规则适用于所有命名空间,包括宏名,且31字符的限制在替换前后均适用。
注意:易混淆的字符如1(数字一)与l(小写字母L)、0与O、2与Z、5与S、n与h应谨慎使用。
规则 5.2(必须): 内层作用域中的标识符不应使用外层作用域中标识符的相同名称,从而隐藏外层标识符
解释: “外层作用域”指的是具有文件作用域的标识符,而“内层作用域”指的是具有块作用域的标识符。当内层定义覆盖了外层定义时,才构成违规。
示例: 如果使用了第三方库,应在文档中列出该库的版本,确认其与MISRA-C标准的兼容性,并记录任何必要的验证测试结果。
int i; // 外层定义
{
int i; // 内层定义,隐藏了外层的i,不合规
i = 3; // 可能导致混淆,不清楚引用的是哪一个'i'
}
规则 5.3(必须): 类型定义名应当是一个唯一的标识符
解释: 此规则要求类型定义名(typedef
名称)必须在整个程序中是唯一的,不得在任何地方被重用,无论作为其他 typedef
名称还是用于任何其他目的。即使在不同的源文件中,也不允许出现相同的类型定义,即使它们的声明完全相同。当类型定义在头文件中声明,且该头文件被多个源文件包含时,不违反此规则。
示例:
typedef unsigned char uint8_t; // 合规
// 下列均为不合规
typedef unsigned char uint8_t; // 重定义uint8_t
unsigned char uint8_t; // 重用uint8_t
规则 5.4(必须): 标签名应当是一个唯一的标识符
解释:此规则禁止在程序中重复使用标签名(struct 或 union 的名字),无论是用于定义不同的标签还是用于其他任何目的。当一个聚合类型声明使用同一标签在不同类型限定符(struct 或 union)中时,C90标准未定义其行为。因此,所有使用同一标签的地方应当要么全部使用 struct 类型限定符,要么全部使用 union 类型限定符。
示例:
typedef unsigned char uint8_t; // 合规
// 下列均为不合规
typedef unsigned char uint8_t; // 重定义uint8_t
unsigned char uint8_t; // 重用uint8_t
规则 5.5(建议): 静态存储期的对象或函数标识符不应该被重用
解释:此规则虽然为建议性规则,但强调无论作用域如何,具有静态存储期的任何标识符(包括具有外部链接的全局对象或函数,以及使用静态存储类的局部对象或函数)都不应在系统的任何源文件中被重用。
规则 5.6(建议): 不同命名空间中的标识符不应具有相同的拼写,结构体成员和联合体成员名称除外
解释:此规则虽然为建议性规则,但强调无论作用域如何,具有静态存储期的任何标识符(包括具有外部链接的全局对象或函数,以及使用静态存储类的局部对象或函数)都不应在系统的任何源文件中被重用。尽管编译器可以正确处理这种情况,但存在用户将同名但不相关变量错误关联的风险,尤其是在一个文件中有内部链接的标识符,而在另一个文件中有同名的外部链接标识符。
示例:
struct { int16_t key; int16_t value; } record;
int16_t value; /* 违反规则 - value的第二次使用 */
record.key = 1;
value = 0; /* 应该是record.value */
相比之下,下面的例子没有违反规则,因为结构体成员名称不太可能被混淆:
struct device_q { struct device_q *next; /* ... */ }
devices[N_DEVICES];
struct task_q { struct task_q *next; /* ... */ }
tasks[N_TASKS];
devices[0].next = &devices[1];
tasks[0].next = &tasks[1];
规则 5.7(建议): 任何标识符名称都不应被重用
解释:此规则进一步强调,无论作用域如何,任何标识符都不应在系统中的任何文件中被重用。这涵盖了规则5.2、5.3、5.4、5.5和5.6的规定。例如,即使单位不同,结构体成员中也不应使用相同的标识符名称。
示例:
struct air_speed
{
uint16_t speed; /* knots */
} * x;
struct gnd_speed
{
uint16_t speed; /* mph */
/* 不合规 - 单位不同 */
} * y;
x->speed = y->speed;
在这个例子中,虽然 speed 成员在两个结构体中表示不同的物理量,但使用相同的名称可能导致混淆。
参考文献:MISRA-C手册
总结
通过避免在不同命名空间中使用相同的标识符名称,以及在整个系统中避免标识符的重用,可以显著减少代码中的潜在错误和混淆,提高代码的可读性和可维护性。在实际开发中,采用严格的命名约定,如使用描述性强的名称和清晰的命名模式,可以帮助实施这些规则。此外,当标识符名称在头文件中使用,并且该头文件被多个源文件包含时,这些规则不视为被违反。