C语言编程规范这部分一直想总结一下。现在终于付诸行动了。
其实之前讲过一些面试题,参看:嵌入式面试知识点总结 -- C语言篇
里面已经有包含一部分了,比如《高质量C++ C编程指南》.林锐着.pdf。
此次主要参考 华为技术有限公司c语言编程规范
和 MISRA C2012
再详细讲一下C语言的编程规范。
下载:编程规范
这篇文章主要以MISRA C 2012 中文版为基础,再将华为C语言编程规范融入其中。
看来之前总结两年的 C语言再学习 专栏,又有用武之地了。
一、名词解释
1、声明(declare)和定义(define)
声明一个变量只是将变量名标识符的有关信息告诉编译器,使编译器"认识"该标识符,但声明不一定引起内存的分配 。而定义变量意味着给变量分配内存空间,用于存放对应类型的数据,变量名就是对相应的内存单元的命名。在C/++程序中,大多数情况下变量声明也就是变量定义,声明变量的同时也就完成了变量的定义,只有声明外部变量时例外。函数类似,声明只是告诉编译器有这个名称、类型的函数,而定义则是函数的真实实现。
简单一句话,定义创建了对象并为这个对象分配了内存,声明没有分配内存。
以下这些就是声明:
c
extern int bar;
extern int g(int, int);
double f(int, double); // 对于函数声明,extern关键字是可以省略的。
class foo; // 类的声明,前面是不能加class的。
与上面的声明相应的定义如下:
c
int bar;
int g(int lhs, int rhs) {return lhs*rhs;}
double f(int i, double d) {return i+d;}
class foo {};
2、连接/链接(linkage)
分为三类,外部连接(链接)(external linkage)、内部连接(链接)(internal linkage)和无连接(链接)(no linkage)。
- 外部连接(链接)(external linkage):对于变量,即无"static"修饰的全局可访问的变量;对于函数,即无"static"修饰的全局可调用的函数。它们即使没有在头文件中用"extern"做外部声明,仍然被识别为外部连接(链接)(external linkage)。
- 内部连接(链接)(internal linkage):即由"static"修饰的全局变量和函数,它们尽可在所在文件内访问和调用,无法被全局访问/调用。
- 无连接(链接)(no linkage):即函数内部变量。所有函数都是有连接(链接,linkage)的。内部变量包含临时变量和静态变量两种,它们的共同特征是均无法在本函数外被访问。
外部链接:
一个具有外部链接的变量可以在一个多文件程序的任何地方使用。
c
int n = 5; /*文件作用域,外部链接,未使用 static */
int main (void)
{
...
return 0;
}
内部链接:
一个具有内部链接的变量可以在一个文件的任何地方使用。
c
static int dodgers = 3; /*文件作用域,内部链接,使用了 static ,该文件所私有*/
int main (void)
{
...
return 0;
}
空链接:
具有代码块作用域或者函数原型作用域的变量有空链接,意味着它们是由其定义所在的代码块或函数原型所私有的。
c
double blocky (double cleo)
{
double patcrick = 0.0; /*代码块作用域,空链接,该代码块所私有*/
int i;
for (i = 0; i < 10; i++)
{
double q = cleo * i; /*q作用域的开始*/
...
patrick * = q;
} /*q作用域的结束*/
return patrick;
}
3、对象(object)
本规范的编制,具有普适性,故会出现如"对象"、"类"这些标准 C 中不提及的概念,对象在 C 语言中的直接对应是变量。当前对象不仅仅是变量,但本译文仅限考虑标准 C(准确的说是嵌入式 C),故不过多描述,我们将其当成"变量"理解即可。
二、规则
每条 MISRA C 准则都可以被归类为"规则"或"指令"。
- 规则:仅对源代码进行分析,即可对规则进行合规性判定,静态分析工具应该具有判定规则的能力,不需要结合人工判定。
- 指令:仅对源代码进行分析,无法对指令进行合规性判定,往往需要结合设计文档或开发人员的经验进行综合判定,静态分析工具可能提供辅助,但不同性能的工具提供的解释可能大不相同。
MISRA C2012将规则和指令均分为三个级别:
- 强制类:必须满足;
- 必要类:应该满足,若不满足应该说明原因;
- 建议类:应该满足,若不满足应该说明原因。
1、标准 C 环境(A standard C environment)
Rule 1.1 程序不得违反标准 C 语法和约束,并且不得超出具体实现的编译限制
- 级别:必要
- 解读:程序应仅使用所选标准版本中指定的 C 语言及其库的功能
- 示例:比如你用C90的编译器你就要符合C90的规则和特性,而不能去使用到C11的特性,其实即使你使用了,编译器也不支持,但不一定都能检测出来,具有一定的风险。
Rule 1.2 不应该使用语言扩展
- 级别:建议
- 解读:不要用编程语言扩展属性,否则会降低程序的可移植性
- 示例: if ((NULL != FuncPointer) && (*FuncPointer())),这样的语句是符合语法的,且就是利用了"一旦确定结果立即停止评估"的特性,在 FuncPointer 值为
NULL 时执行"&&"的右操作数会非常危险,程序会跑到哪完全不可预知。
Rule 1.3 不得发生未定义或严重的未指定行为
- 级别:必要
- 解读:一些未定义或未指定的行为有特定的规则处理。此规则意在防止其他未定义和关键的未指定行为。MISRA C 的许多准则旨在避免某些未定义和未指定的行为。 例如,遵守 Rule 11.4、Rule 11.8 和 Rule 19.2 的所有内容可确保在
C 中不能创建指向使用 const 限定类型声明的对象的非 const 限定指针。这避免了 C90 [Undefined 39]和 C99
[Undefined 61]。
2、未使用的代码(Unused code)
Rule 2.1 项目不得包含不可达代码(unreachable code)
- 级别:必要
- 解读:如果一个程序没有表现出任何未定义的行为,那么无法到达的代码就不能被执行,也不能对程序的输出产生任何影响。因此,无法到达的代码的存在可能表明程序逻辑中的错误。
无法到达的代码会占用目标机器的内存空间,可能会导致编译器在围绕无法到达的代码传输控制时选择更长的、更慢的跳转指令。而且在循环中,它可以防止整个循环驻留在指令缓存中。- 示例:switch中某个case分支是永远运行不到的,程序员应该删除这种代码。
c
enum light { red, amber, red_amber, green };
enum light next_light ( enum light c )
{
enum light res;
switch ( c )
{
case red:
res = red_amber;
break;
case red_amber:
res = green;
break;
case green:
res = amber;
break;
case amber:
res = red;
break;
default:
{
/* 当参数 c 的值不是枚举型 light 的成员时, 此 default 分支才可达 */
error_handler ( );
break;
}
}
return res;
res = c; /* 违规 - 此语句肯定不可达 */
}