文章目录
- 8.14控制语句表达式
- 规则14.1循环计数器本质上不能是浮点型
- Rule 14.2 for 循环应为良好格式
- Rule 14.3 控制表达式不得是值不变的
- Rule 14.4 if 语句和循环语句的控制表达式的基本类型应为布尔型
8.14控制语句表达式
本节中的一些规则使用了术语循环计数器。循环计数器需要定义为满足以下条件的对象、数组元素或结构体或联合的成员:
- 它具有标量类型;
- 其值在给定循环实例的每次循环中单调变化;
- 参与退出循环的判定。
注意:第二个条件意味着循环计数器的值必须在循环的每次迭代中改变,并且对于给定的循环实例,它必须总是在相同的方向上改变。但是,在不同的实例上,它可能在不同的方向上发生变化,例如,有时向后读取数组的元素,有时向前读取它们。
根据这个定义,一个循环不需要只有一个循环计数器:一个循环可以没有循环计数器,也可以有多个循环计数器。有关for循环中循环计数器的更多限制,请参见规则14.2
下面的代码片段展示了循环及其对应的循环计数器的示例。
在这个循环中,i是一个循环计数器,因为它具有标量类型,单调变化(递增),并且涉及循环终止条件。
for ( uint16_t i = 0; a[ i ] < 10; ++i )
{
}
下面的循环没有循环计数器。对象计数具有标量类型且单调变化,但不涉及终止条件。
extern volatile bool_t b;
uint16_t count = 0;
while ( b )
{count = count + 1U;
}
以下代码中,i 和 sum 均为标量且单调变化(分别减小和增大)。但 sum 并不是循环计数器,因为它不参与退出循环的判定。
uint16_t sum = 0;
for ( uint16_t i = 10U; i != 0U; --i )
{sum += i;
}
在接下来的循环中,p是一个循环计数器。它不涉及循环控制表达式,但它涉及通过break语句退出循环的决定。
extern volatile bool_t b;
extern char *p;
do
{if ( *p == '\0' ){break;}++p;
} while ( b );
下面示例中的循环计数器是p->count。
struct s
{uint16_t count;uint16_t a[ 10 ];
};
extern struct s *p;
for ( p->count = 0U; p->count < 10U; ++( p->count ) )
{p->a[ p->count ] = 0U;
}
规则14.1循环计数器本质上不能是浮点型
等级:必要
分析:不可判定,系统范围
适用:C90,C99
原理:当使用浮点循环计数器时,舍入误差的累积可能导致预期迭代次数与实际迭代次数不匹配。当一个不是浮点基数幂的循环步长舍入到一个可以表示的值时,就会发生这种情况。
即使带有浮点循环计数器的循环在一个实现上表现正确,它也可能在另一个实现上给出不同的迭代次数。(不同编译器设置,浮点数取整方式可能不一样)
示例:
在下面的违规示例中,在循环结束时counter的值可能不是1 000
uint32_t counter = 0u;
for ( float32_t f = 0.0f; f < 1.0f; f += 0.001f )
{++counter;
}
下面的合规示例使用整数循环计数器来保证 1000 次循环,并在循环内使用它生成 f。
float32_t f; for (uint32_t counter = 0u; counter < 1000u; ++counter)
{
f = (float32_t)counter * 0.001f;
}
下面的 while 循环违规,因为浮点数 f 被用作循环计数器
float32_t f = 0.0f; while (f < 1.0f)
{
f += 0.001f;
}
下面的 while 循环合规,因为浮点数 f 未被用作循环计数器。
float32_t f;
uint32_t u32a;
f = read_float32();
do
{
u32a = read_u32();
/* f 在循环内未被修改, 所以不作为循环计数器处理 */
} while (((float32_t)u32a - f) > 10.0f);
解读:该规则需要被实施,不过应该一般编程中都不会使用浮点数作为循环的变量
Rule 14.2 for 循环应为良好格式
等级:必要
分析:不可判定,系统范围
适用:C90,C99
展开:for 循环语句包含三个子句,此规则对它们的要求为:第一个子句
◆ 不得为空,或
◆ 应为循环计数器分配一个值,或
◆ 应定义并初始化循环计数器(C99)。
第二个子句
◆ 应该是没有持久副作用的表达式,并且
◆ 应使用循环计数器和可选的循环控制标志,并且
◆ 不得使用在 for 循环主体中修改的任何其他对象。
第三个子句
◆ 应该是一个表达式,其唯一的持久副作用是修改循环计数器的值,并且
◆ 不得使用在 for 循环主体中修改的对象。
for循环中只能有一个循环计数器,不能在for循环体中修改。
循环控制标志定义为:在第二个子句使用的基本型为布尔型的对象的单个标识符。
for 循环主体的行为包括了在该主体内调用的所有函数的行为。
原理:for 循环语句提供了一种通用循环工具。使用形式受限的循环可使代码更易于查看和分析
例外:三个子句都可以为空,例如 for (;; ),以便允许无限循环。
示例:在下面的 C99 示例中,i 是循环计数器,flag 是循环控制标志。
bool_t flag = false;
for ( int16_t i = 0; ( i < 5 ) && !flag; i++ )
{if ( C ){flag = true; /* 合规 - 允许提前终止循环 */}i = i + 3; /* 违规 - 循环主体内不允许改变循环计数器 */
}
解读:for循环格式必须要遵守,该规则需要被实施
Rule 14.3 控制表达式不得是值不变的
等级:必要
分析:不可判定,系统范围
适用:C90,C99
展开:此规则适用于:
◆ if,while,for,do…while 和 switch 语句的控制表达式;
◆ ?:运算符的第一个操作数。
原理:如果控制表达式的值不变,则意味着可能存在编程错误。因存在不变表达式而无法到达的任何代码都可以由编译器删除。例如,这可能会导致从可执行文件中删除防御性代码。
例外:
- 允许使用用于创建无限循环的不变的控制表达式。
- 允许 do…while 循环使用值为 0 的布尔型控制表达式。
示例
s8a = ( u16a < 0u ) ? 0 : 1;/* 违规- u16a总是>= 0 */
{
}
if ( u16a <= 0xffffu )
{/* 违规 - always true */
}
if ( 2 > 3 )
{/* 违规 - always false */
}
for ( s8a = 0; s8a < 130; ++s8a )
{/* 违规 - always true */
}
if ( ( s8a < 10 ) && ( s8a > 20 ) )
{/* 违规 - always false */
}
if ( ( s8a < 10 ) || ( s8a > 5 ) )
{/* 违规 - always true */
}
while ( s8a > 10 )
{if ( s8a > 5 ) {/* 违规 - s8a not volatile */}
}
while ( true )
{/* 例外1,合规 */
}
do
{/* 例外2,合规 */
} while ( 0u == 1u );
const uint8_t numcyl = 4u;
/** 违规 - 条件一直满足*/
if ( numcyl == 4u )
{
}
const volatile uint8_t numcyl_cal = 4u;
/** 合规- volatile 属性可能导致外部修改其值*/
if ( numcyl_cal == 4u )
{
}
uint16_t n; /* 10 <= n <= 100 */
uint16_t sum;
sum = 0;
for ( uint16_t i = ( n - 6u ); i < n; ++i )
{sum += i;
}
if ( ( sum % 2u ) == 0u )
{/** 违规 - sum是6个连续的非负整数的和, 因此必为奇数, if语句的控制表达式将始终为false */
}
解读:该规则需要被实施,可以检查出异常的控制代码
Rule 14.4 if 语句和循环语句的控制表达式的基本类型应为布尔型
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:for 循环语句的控制表达式是可选的。 此规则不要求表达式存在,但如果存在,则要求表达式的基本类型为布尔型
原理:强类型要求if语句或迭代语句的控制表达式本质上具有布尔类型
示例:
int32_t *p, *q;
while ( p ) /* 违规 - p 为指针 */
{
}
whil e ( q != NULL ) /* 合规 */
{
}
while ( true ) /* 合规 */
{
}
extern bool_t flag;
while ( flag ) /* 合规 */
{
}
int32_t i;
if ( i ) /* 违规 */
{
}
if ( i != 0 ) /* 合规 */
{
}
解读:该规则需要被实施,但在检查过程中,可能由于bool量无法被正确识别,导致可能会误报出该项。