声明
Section outline
-
带标签语句(Labeled Statements)
在 K&R 中,标签名与“普通”标识符共享同一个命名空间。也就是说,如果在一个内层代码块中声明了一个与标签同名的标识符,那么该标签名就会被隐藏。例如:
void f() { label: ; { int label; … goto label; } }
这个代码会在编译时报错,因为
goto
所跳转的目标label
被视为一个整型变量而不是标签。而在标准 C 中,标签有自己的独立命名空间,因此上面的代码可以正常编译,不会报错。
K&R 指定内部标识符(如标签)的有效长度为 8 个字符。
而标准 C 要求内部标识符(包括标签)的有效长度至少为 63 个字符。
复合语句(Compound Statement / Block)
在 C99 之前,代码块中的所有声明必须位于所有语句之前。但从 C99 开始,声明和语句可以交错出现。
C++ 注意事项:C++ 也允许声明和语句交错使用。
使用
goto
或switch
可以跳转进入一个代码块。虽然这样是可移植的,但是否能正确初始化被“跳过”的自动变量则未作保证。K&R 允许嵌套代码块,但未说明嵌套的深度限制。
C89 要求复合语句至少支持嵌套 15 层,C99 将此限制提升到了 127 层。
表达式语句和空语句(Expression and Null Statements)
以下是一个使用
volatile
类型限定符的示例(此限定符由 C89 引入):extern volatile int vi[5]; void test2() { volatile int *pvi = &vi[2]; vi[0]; pvi; *pvi; *pvi++; }
当处理带有
volatile
限定的对象时,优化器必须非常小心,因为不能对这些对象的状态作出任何假设。即使像*pvi;
这样的语句,看似没有实际用途,编译器也可能会生成访问vi[2]
的代码,这可能涉及到硬件同步。但pvi;
语句不会被执行任何访问操作,因为pvi
本身不是volatile
。建议:不要依赖表达式语句如
i[0];
、pi;
或*pi;
来生成代码。即使i
是一个volatile
对象,也不能保证一定会进行访问。
选择语句(Selection Statements)
if
语句if
控制表达式是完整表达式,因此在其后有一个序列点(sequence point)。switch
语句K&R 要求控制表达式及每个
case
表达式的类型必须是int
。标准 C 放宽了限制,要求它们是某种整型。
case
表达式会在必要时转换为与控制表达式相同的类型。由于标准 C 支持枚举类型(以整数形式表示),可以在
switch
和case
中使用枚举常量(K&R 中没有枚举类型)。某些实现支持case
表达式中的区间语法,但因语法不统一,标准 C 不支持该特性。标准 C 允许多字符字符常量(如
'ab'
、'abcd'
)用于case
中。建议:由于多字符常量的内部表示是实现定义的,不应在
case
中使用多字符字符常量。K&R 没有规定
switch
中最多允许多少个case
。C89 要求至少支持 257 个,C99 将此数量提升至 1023 个。switch
控制表达式是完整表达式,因此在其后存在一个序列点。关于
switch
中跳转到复合语句的问题,见上文“复合语句”。
循环语句(Iteration Statements)
在
while
、do
和for
语句中的控制表达式可能包含如expr1 == expr2
的浮点比较表达式。由于浮点运算涉及舍入等实现定义行为,表达式结果可能难以精确相等。建议:控制表达式中如需比较浮点值,考虑使用类似
fabs(expr1 - expr2) < 0.1e-5
这样的方式代替expr1 == expr2
。有些程序中会出现“空转循环”(idle loop),例如:
for (i = 0; i <= 1000000; ++i) { }
为解决这类空循环的问题,C11 引入以下规则:如果循环控制表达式不是常量表达式,且其循环体中没有输入/输出操作、未访问
volatile
对象、未进行同步或原子操作,那么编译器可以假定该循环会终止,并可以将其优化掉。比如上述示例中,编译器可以省略整个循环,只保留使i
最终变为1000001
的效果。建议:不要使用“空转循环”来实现延时。这类循环即使没有被优化掉,其执行时间也极易受系统任务优先级和处理器速度影响。
标准 C 保证控制结构和复合语句至少支持 15 层嵌套,C99 提升到 127 层。K&R 未说明嵌套层数下限。
while
语句控制表达式是完整表达式,后有序列点。
do
语句控制表达式是完整表达式,后有序列点。
for
语句三部分表达式均为完整表达式,三者之后均有序列点。
C99 支持在
for
的第一个表达式中进行声明,例如int i = 0;
,而不再要求变量i
事先定义。C++ 注意事项:C++ 也支持这一 C99 特性,但在
for
中声明的变量作用域规则与 C 不同。
跳转语句(Jump Statements)
goto
语句关于标签命名空间的说明,见前文“带标签语句”。关于跳转进入代码块的问题,见“复合语句”部分。
return
语句当使用
return expression;
形式时,expression
是完整表达式,之后存在序列点。若函数声明有返回值,但实际未返回任何值,行为未定义(C99 起,对
main
函数除外,若无明确返回则视为return 0;
)。标准 C 支持
void
函数类型,允许编译器验证函数不返回值(K&R 不支持 void 类型)。标准 C 还支持按值返回结构体与联合体,对返回对象大小无强制限制,虽然传值的大小可能受限。K&R 不支持这种返回方式。
K&R 第 68 页和第 70 页示例中使用
return(expression);
,而第 203 页的正式语法为return expression;
。这看似矛盾,实则不然:第 203 页是正确的,括号只是表达式中的冗余分组符号,不属于语法的一部分。示例中使用括号是出于风格考虑,便于将return
与表达式分开。第二版 K&R 已移除括号,并通常使用return 0;
结束main
函数。C99 还添加了一条限制:“不带表达式的 return 语句只能出现在返回类型为 void 的函数中。”