文章目录
-
- [8.15 控制流(Control flow)](#8.15 控制流(Control flow))
-
- [Rule 15.1 不应使用 goto 语句](#Rule 15.1 不应使用 goto 语句)
- [Rule 15.2 goto 语句仅允许跳到在同一函数中声明的稍后位置的标签](#Rule 15.2 goto 语句仅允许跳到在同一函数中声明的稍后位置的标签)
- [Rule 15.3 goto 语句引用的标签必须在 goto 语句所在代码块或包含该代码块的上级代码块中声明](#Rule 15.3 goto 语句引用的标签必须在 goto 语句所在代码块或包含该代码块的上级代码块中声明)
- [Rule 15.4 最多只能有一个用于终止循环语句的 break 或 goto 语句](#Rule 15.4 最多只能有一个用于终止循环语句的 break 或 goto 语句)
- [Rule 15.5 应仅在函数的末尾有单个函数出口](#Rule 15.5 应仅在函数的末尾有单个函数出口)
- [Rule 15.6 循环语句和选择语句的主体应为复合语句](#Rule 15.6 循环语句和选择语句的主体应为复合语句)
- [Rule 15.7 所有的 if...else if 构造都应以 else 语句结束](#Rule 15.7 所有的 if…else if 构造都应以 else 语句结束)
8.15 控制流(Control flow)
Rule 15.1 不应使用 goto 语句
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
原理:不受约束地使用goto会导致程序的非结构化和极难理解
在某些情况下,全面禁止goto需要引入flag以确保正确的控制流,而但这些标志本身可能不如它们替换的goto 清晰。因此,如果不遵守此规则,则允许在遵守规则15.2和规则15.3的指导下限制使用goto
解读:该规则需要被实施,嵌入式代码中不应使用goto
Rule 15.2 goto 语句仅允许跳到在同一函数中声明的稍后位置的标签
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
原理:不受约束地使用goto会导致程序的非结构化和极难理解
限制goto的使用以禁止"back"跳转,确保迭代只在使用语言提供的迭代语句时发生,从而帮助最小化可视化代码的复杂性
示例:
void f ( void )
{
int32_t j = 0;
L1:
++j;
if ( 10 == j )
{
goto L2; /* 合规 */
}
goto L1; /* 违规 */
L2 :
++j;
}
解读:如果要使用goto的话,只能往后面的标签跳,不能往前跳。
Rule 15.3 goto 语句引用的标签必须在 goto 语句所在代码块或包含该代码块的上级代码块中声明
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:为符合此规则的设计目的,将不包含复合语句(复合语句:{}括起来的语句)的 switch 子句也视为一个代码块(即不用{}括起来的 case 子句也被视为独立的代码块)
原理:不受约束地使用goto会导致程序的非结构化和极难理解
防止块之间或嵌套块之间的跳转有助于最小化可视化代码的复杂性
注意 :当使用变量修改类型时,C99的限制更大。试图从具有可变修改类型的标识符的作用域外跳转到该作用域将导致违反约束。
示例:
void f1 ( int32_t a )
{
if ( a <= 0 )
{
goto L2; /* 违规 - L2 在其他代码块中 */
}
goto L1; /* 合规 - L1 在同代码块 */
if ( a == 0 )
{
goto L1; /* 合规 - L1 在包含此goto语句的上级代码块中 */
}
goto L2; /* 违规 - L2 在子代码块中 */
L1:
if ( a > 0 )
{
L2:
;
}
}
在下面的示例中,标签 L1 在包含 goto 语句的块的块中定义。但是,发生了从一个swtich 子句到另一个 switch 子句的跳转,而由于此规则的目的,将 switch 的子句视为代码块,因此这个 goto 语句是违规的。
switch ( x )
{
case 0:
if ( x == y )
{
goto L1;
}
break;
case 1:
y = x;
L1:
++x;
break;
default:
break;
}
解读:该规则需要被实施,最好是不用goto
Rule 15.4 最多只能有一个用于终止循环语句的 break 或 goto 语句
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
原理:限制循环的退出数量有助于最小化可视化代码的复杂性。当需要提前终止循环时,使用一个break或goto语句可以创建一个次要退出路径。
示例:下面嵌套的两个循环均合规,因为每个循环都只有一个 break 语句用于提前终止循环。
for ( x = 0; x < LIMIT; ++x )
{
if ( ExitNow ( x ) )
{
break;
}
for ( y = 0; y < x; ++y )
{
if ( Exit Now ( LIMIT - y ) )
{
break;
}
}
}
下面循环违规,因为有多个 break 和 goto 语句用于提前终止循环。
for ( x = 0; x < LIMIT; ++x )
{
if ( BreakNow ( x ) )
{
break;
}
else if ( GotoNow ( x ) )
{
goto EXIT;
}
else
{
KeepGoing ( x );
}
}
EXIT:
;
在下面示例中,内部 while 循环 是合规的,因为它只有一个 goto 语句可能导致其提前终止。 但是, 外部 while 循环 是违规的,因为它可以通过 break 语句或内部 while 循环中的 goto 语句提前终止。
while ( x != 0u )
{
x = calc_new_x ( );
if ( x == 1u )
{
break;
}
while ( y != 0u )
{
y = calc_new_y ( );
if ( y == 1u )
{
goto L1;
}
}
}
L1:
z = x + y;
解读:一个循环中只能有一个break,保证代码可读性
Rule 15.5 应仅在函数的末尾有单个函数出口
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
展开:一个函数最多只能有一个 return 语句。使用 return 语句时,它应该是复合语句构成的函数主体的最后一条语句。
原理:IEC 61508 和 ISO 26262 都要求函数有单出口,这是模块化方法要求的一部分。
提早返回可能会导致意外删除函数终止代码。
如果函数的出口点散布着会产生持久副作用的语句,则很难确定执行函数时将发生哪些副作用。
示例:在下面的违规代码示例中,提前返回被用于验证函数形参。
bool_t f ( uint16_t n, char *p )
{
if ( n > MAX )
{
return false;
}
if ( p == NULL )
{
return false;
}
return true;
}
解读:该规则可以不被实施,实际会出现提前函数返回的情况
Rule 15.6 循环语句和选择语句的主体应为复合语句
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:循环语句(while,do...while 或 for)或选择语句(if,else,switch)的主体应为复合语句({}括起来的语句)
原理:开发人员可能会错误地认为,由于一系列语句的缩进,它们构成了迭代语句或选择语句的主体。控制表达式后面意外包含分号是一种特殊的危险,会导致控制语句为空。
使用复合语句清楚地定义了哪些语句实际上构成了主体。
此外,缩进可能会导致开发人员将else语句与错误的if语句 关联起来
**例外 :**如果 if 语句紧随 else 出现,则这个 if 语句不必被包含在符合语句中(即允许 else if 语句出现)
示例:复合语句的布局及包围它的花括号位置是样式问题,本文档不解决。 以下示例中使用的样式不是强制性的。
while (data_available)
process_data(); /* 违规,没有加括号*/
while (data_available)
process_data(); /* 违规,没有加括号*/
service_watchdog();
其中service_watchdog()应该被添加到循环体中。复合语句的使用显著地减少了发生这种情况的机会
下面示例中,action_2()看上去是第一个 if 的 else 语句。
if ( flag_1 )
if ( flag_2 ) /* 违规,没有加括号 */
action_1 ( ); /* 违规,没有加括号 */
else
action_2 ( ); /* 违规,没有加括号 */
而它的实际行为是:
if ( flag_1 )
{
if ( flag_2 )
{
action_1 ( );
}
else
{
action_2 ( );
}
}
复合语句的使用可确保明确定义 if 和 else 间的关联。
例外允许使用else if,如下所示
if ( flag_1 )
{
action_1 ( );
}
else if ( flag_2 ) /* 合规 - 符合例外 */
{
action_2 ( );
}
else
{
;
}
以下示例显示了意外的分号如何导致错误:
while ( flag ); /* 违规,分号导致可能出现死循环 */
{
flag = fn ( );
}
以下示例显示了循环主体为空的合规方法:
while (!data_available)
{
}
解读:该规则需要被实施,一方面可以提高代码可读性,另一方面,可以检查出部分错误代码,例如while错误的分号
Rule 15.7 所有的 if...else if 构造都应以 else 语句结束
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:当 if 语句后接一个或多个 else if 构造的序列时,应始终以 else 语句作为结尾。 else 语句至少应包含一个副作用或注释。
原理:用 else 语句终止 if...else if 序列的序列是防御性编程,相似的,还有 switch 语句中对 default 子句的要求(请参见规则 16.5)。
else 语句必须具有副作用或注释,以确保对所需的行为给出肯定的指示,以帮助代码检查过程。
注意:简单的if语句不需要最后的else语句
示例:以下违规示例中,没有明确使用 else 语句结尾以指示不再执行任何操作
if ( flag_1 )
{
action_1 ( );
}
else if ( flag_2 )
{
action_2 ( );
}/* 违规,无else */
下面显示了一个符合标准的终止 else 示例。
else
{
; /* No action required - ; is optional */
}
解读:该规则目的主要是增加代码可读性,else里即使没有操作,也要进行注释来明确。实际实施的意义不大