等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:程序只能使用选定的标准版本(参见第3.1节)中指定的C语言及其库的那些特性。
该标准允许实现提供语言扩展,并且该规则允许使用这种扩展。
除非使用语言扩展,否则程序不得有以下行为:
•包含任何违反标准中描述的语言语法的内容;
•包含任何违反标准所施加的约束
程序不得超过实施方规定的编译限制。最低编译限制由标准规定,但实现可能提供更高的限制。
注意:符合标准的实现会生成语法和约束违规的诊断,但要注意:
•诊断不一定是错误,也可以是一个警告;
•程序可以被编译并生成可执行文件,尽管存在语法或约束违反;
注意:当超过编译限制时,符合标准的实现不需要生成诊断;可以生成可执行文件,但不能保证正确执行。
原理:与ISO/IEC 9899支持版本之外的语言特性相关的问题在本指南的开发过程中未被考虑。
有一些轶事证据表明,一些不符合标准的实现未能诊断约束违反,例如在[38]p135中,题为“写入常量区域的错误”的示例2。
示例:一些C90编译器使用__inline关键字提供对内联函数的支持。使用__inline的C90程序将遵守此规则,前提是它打算使用这样的编译器进行编译。
许多用于嵌入式目标的编译器提供了附加的关键字,例如,这些关键字用对象所在的内存区域的属性来限定对象类
•__zpage:对象可以使用短指令访问
•__near:指向对象的指针可以保留16位
•__far:指向对象的指针可以保留24位
使用这些附加关键字的程序将遵循此规则,前提是编译器支持将这些关键字作为语言扩展。
等级:建议
分析:不可判定,单一编译单元
适用:C90,C99
原理:依赖于语言扩展的程序可能比不依赖于语言扩展的程序更不容易移植。虽然标准要求一个符合标准的实现文档记录它提供给该语言的任何扩展。但还是存在这样的风险,即该文档可能无法在所有情况下提供对行为的完整描述。
如果不应用此规则,则应该在项目的设计文档中证明使用每种语言扩展的决定是合理的。还应该记录确保有效使用每个扩展的方法,例如检查编译器及其诊断
人们认识到在嵌入式系统中使用语言扩展是必要的。标准要求扩展不能改变任何严格符合标准的程序的行为。例如,编译器可以作为扩展实现对二进制逻辑运算符的完整求值,尽管标准规定一旦可以确定结果就立即停止求值。这样的扩展不符合标准,因为逻辑与操作符的右操作数总是会出现副作用,从而导致不同的行为(可能出现异常)。
等级:必要
分析:不可判定,系统范围
适用:C90,C99
展开:一些未定义的需要和未指定的行为由特定的规则处理。此规则防止所有其他未定义的需要和关键的未指定行为。附录H列出了未定义的必需行为和那些被认为是关键的未指定行为。
原理:任何产生未定义或未指定行为的程序都可能不会以预期的方式运行。在许多情况下,其结果是使程序不可移植,但也有可能发生更严重的问题。例如,未定义的行为可能会影响计算的结果。
如果软件的正确操作依赖于此计算,则系统安全可能受到损害。如果未定义的行为只在极少数情况下出现,那么问题就特别难以发现。
许多MISRA C指南的设计都是为了避免某些未定义和未指定的行为。例如,遵守所有规则11.4、规则11.8和规则19.2确保在C中不可能创建指向用const限定类型声明的对象的非const限定指针。
这避免了C90 [undefini ned 39]和C99 [undefini ned 61]。然而,其他行为没有被特定的指导方针所涵盖,例如,因为
•不太可能会遇到这种行为;
•除了应该避免这种行为的明显声明之外,没有任何实际的指导可以给出
MISRA C没有为每个未定义的需要和关键的未指定行为引入指导方针
指导方针直接针对那些被认为最重要和最有可能在实践中发生的问题。那些没有具体指导方针的行为都包含在这一条规则中。附录H列出了所有未定义的和关键的未指定行为,以及防止其发生的MISRA C指南。因此,它指示了哪些行为应该被这条规则所阻止,哪些行为被其他规则所覆盖。
注意:一些实现可能为标准中列出的一些未定义和未指定的行为提供定义良好的必需行为。如果依赖于这些定义良好的行为,包括通过语言扩展,则有必要在这些行为方面偏离此规则。
等级:必要
分析:不可判定,系统范围
适用:C90,C99
原理:如果程序没有表现出任何未定义的行为,那么不可达代码就不能执行,也不会对程序的输出产生任何影响。因此,出现不可达代码可能表明程序逻辑中存在错误。
一般编译器会在编译时移除所有不可达代码,尽管它并非必须这么做。而那些无法被编译器移除的不可达代码将造成资源浪费,比如:
•它占用目标机器的内存空间;
•它的存在可能会导致编译器选择更长,更慢的跳转指令时,转移控制周围的不可达代码;
•在一个循环中,它可以防止整个循环驻留在指令缓存中
为了处理异常情况,有时需要插入看起来不可访问的代码。例如,在switch语句中,控制表达式的每个可能值都由显式的case覆盖,则应根据规则16.4提供默认子句。default子句的目的是捕获一个通常不应该出现,但可能由于以下情况而产生的值:
•程序中存在未定义的行为;
•处理器硬件故障
如果编译器能够确认 default 子句不可达,则可以将其删除,从而清除该防御措施。如果该防御措施非常重要,则下述两者必须满足其一:要么保证编译器在证实不可达时仍保留该代码,要么采取措施使防御代码可达。前者与本规则有偏差,可能需要检查目标代码或使用单元测试来保证这种偏差。后者通常可以通过易失性(volatile)访问来实现。举例来说,编译器一般在执行 switch 语句前,已经确定了其条件 x 的值是否被 case 子句所覆盖,如:
uint16_t x;
switch (x)
/* 通过强制通过volatile限定左值访问x,编译器必须假定控制表达式可以取任何值: */
switch ( *( volatile uint16_t * ) &x )
注意:被预处理器指令有条件地排除的代码不受此规则的约束,因为它不会出现在编译的后期阶段
示例:
en um 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:
{
/*
* This default will only be reachable if the parameter c
* holds a value that is not a member of enum light.
*/
error_handler ( );
break;
}
}
return res;
res = c; /* Non-compliant - this statement is
* certainly unreachable */
}
最后一行不可能执行,所以违反了本规则。
等级:必要
分析:不可判定,系统范围
适用:C90,C99
展开:不论保留其执行还是被删除,均不会影响影响程序行为的操作,即构成无效代码。此处,我们假定语言扩展所引用的操作始终会对程序行为产生影响。
注意:嵌入式系统的行为通常不仅取决于其行为的性质,还取决于它们发生的时间
注意:不可达代码不是无效代码,因为它们不会被执行。不可达代码与无效代码的差别:不可达代码不会被执行,无效代码会被执行
原理:无效代码的存在,可能表明程序逻辑中存在错误。由于无效代码可能会被编译器删除,因此它可能会引起混乱。
例外:强制转换为 void 是显示声明有意不使用的值。因此,强制转换本身不是无效代码。它被视为使用了操作数的值,所以它不被定性为无效代码。
示例:在此示例中,假定p 指向的对象在其他函数中使用。
extern volatile uint16_t v;
extern char *p;
void f ( void )
{
uint16_t x;
( void ) v;
( int32_t ) v;
v >> 3;
x = 3;
*p++;
( *p )++;
/* 合规 - v 虽有副作用, 但其可被访问, 且强转 void 是被例外
* 允许的 */
/* 违规 - 执行无效 */
/* 违规 - ">>"运算无效 */
/* 违规 - "="运算无效, x 随后未被读取 */
/* 违规 - "*"运算的结果未被使用 */
/* 合规 - *p 自增 */
}
在下面的合规的示例中, _asm 关键字是语言扩展,而不是函数调用操作,因此不是无效代码。
_asm ( "NOP" );
在下面的示例中,函数 g 不包含无效代码,且其本身也不是无效代码,因为它不含任何操作。但是对它的调用无效,因为删除它不影响程序行为。
void g(void)
{
/* 合规 - 此函数中无任何操作 */
}
void h(void)
{
g(); /* 违规 - 该调用可以被移除 */
}
等级:建议
分析:可判定,系统范围
适用:C90,C99
原理:如果一个类型被声明但从未被使用过,对于审阅者来说,无法确定该声明是多余的还是被错误闲置的。
示例:
int16_t unusedtype(void)
{
typedef int16_t local_Type; /* 违规 */
return 67;
}
等级:建议
分析:可判定,系统范围
适用:C90,C99
原理:如果一个类型标签被声明但从未被使用过,对于审阅者来说,无法确定该类型标签是多余的还是被错误闲置的。
示例:在下面的示例中,标记状态未被使用,并且可以在不使用它的情况下编写声明
void unusedtag ( void )
{
enum state { S_init, S_run, S_sleep }; /* Non-compliant */
}
在下面的示例中,标记record_t仅在record_1_t的typedef中使用,当需要该类型时,在翻译单元的其余部分中使用。通过省略record2_t定义中的标记,可以以兼容的方式编写该类型定义。
typedef struct record_t /* Non-compliant */
{
uint16_t key;
uint16_t val;
} record1_t;
typedef struct /* Compliant */
{
uint16_t key;
uint16_t val;
} record2_t;
在使用自定义结构体时,使用typedef时需要省略struct后的标签
等级:建议
分析:可判定,系统范围
适用:C90,C99
原理:如果一个宏被声明但从未被使用过,对于审阅者来说,无法确定该宏是多余的还是被错误闲置的。
void use_macro(void)
{
#define SIZE 4
#define DATA 3 /* 违规 - DATA 未被使用 */
use_int16(SIZE);
}
所有未使用的宏定义都应该删除
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
原理:如果一个执行标签(label)被声明但从未被使用过,对于审阅者来说,无法确定该执行标签是多余的还是被错误闲置的。
示例:
void unused_label(void)
{
int16_t x = 6;
label1: /* 违规 */
use_int16(x);
}
label一般在汽车c软件中用的比较少。
tag 和 label,两者翻译为中文都是标签,差别在于 tag 为枚举、结构体、联合体类型的标签,label
为 goto 语句执行目的地的标签,本文中为区分,将它们分别描述为了类型标签与执行标签。
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
原理:绝大多数函数都将使用它们所定义的每一个参数。如果函数中的参数未被使用,则可能函数的实现与其预期定义不匹配。本规则强化描述了这一潜在的不匹配
void withunusedpara(uint1 6_t *para1, int16_t unusedpara) /* 违规 - 参数未使用 */
{
*para1 = 42U;
}
函数中定义未使用的变量都应该删除。
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
原理:“/*”和“//”均为注释起始的字符序列,如果在一段由“/*”起始的注释中,又出现了“/*”或“//”,那么很可能是由缺少“*/”引起的。
如果这两个注释起始的字符序列出现在由“//”起始的注释中,则很可能是因为使用“//”注释掉了代码。
如果这两个注释起始的字符序列出现在由“//”起始的注释中,则很可能是因为使用“//”注释掉了代码。
例外:“//”起始的注释里,允许再次出现“//”。
示例 :
/* some comment, end comment marker accidentally omitted
<>
Perform_Critical_Safety_Function(X);
/* this comment is non-compliant */
第一段注释中没有*/结尾,导致Perform_Critical_Safety_Function函数也被注释了。
所以注释时,/*后面必须跟*/,而不能跟/*或//,可能会导致意外的注释。
在下面 C99 代码的示例中,“//”的出现改变了程序的含义:
x = y // /*
+ z
// */
;
得到x = y + z;但应该是x = y;如果没有两个//注释开始序列。
//后面也不能跟/*,但可以跟//
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:当“\”字符后紧跟换行符时,将发生行拼接。
原理:如果包含//注释的源行以源字符集中的\字符结尾,则下一行将成为注释的一部分。这可能会导致无意中删除代码
注意:行拼接在C90和C99的第5.1.1.2(2)节中都有描述
示例:在下面的违规示例中,包含 if 关键字的物理行在逻辑上是前一行的一部分,因此是注释
extern bool_t b;
void f(void)
{
uint16_t x = 0; // comment \
if (b)
{
++x; /* if 语句被作为注释处理, 这里无条件执行 */
}
}
使用//注释时,不允许加行拼接符\,可能会意外屏蔽代码